Skip to main content

Module raw

Module raw 

Source
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.