Expand description
Raw SQL, bound safely — RawQuery.
let stale = database
.query("DELETE FROM sessions WHERE last_seen_at < ?")
.bind(cutoff)
.execute()
.await?;
let names: Vec<String> = database
.query("SELECT name FROM users WHERE team_id = ? ORDER BY name")
.bind(team_id)
.route_by(team_id)
.column("name")
.await?;Every ORM needs this door: a
recursive CTE, a window function, a LATERAL join, an EXPLAIN, a
migration’s one-off backfill. Without it the escape hatch is
Database::statement, which takes no bindings at all — and a query that
needs a value with no way to bind one is a query somebody is about to build
with format!.
§Placeholders are always ?
MySQL and SQLite spell a placeholder ?; Postgres spells it $1, $2.
Writing SQL that runs on all three otherwise means writing it twice, which
is the exact madness a DBAL exists to absorb. So ? is the spelling here
and it is rewritten to $n for Postgres, in order, skipping anything
inside a string literal or a quoted identifier.
The one place that bites is Postgres’s JSON ? operator (data ? 'key')
and its ??/?|/?& relatives — genuinely ? characters that are not
placeholders. Reach for raw_placeholders
there and write $1 yourself.
§This is the unsafe door, and it is unsafe in one specific way
Values are always bound — that is what bind is for,
and a bound value can never be SQL. The statement is not: it is
whatever string you passed. Building that string out of anything a request
supplied is an injection, and no amount of binding downstream repairs it.
A table or column name that has to vary belongs in a match over a closed
set, never in a format!.
Structs§
- RawQuery
- A statement being built: the SQL, its bindings, and where to run it.