flawopen.com/SQL Injection in Rust/sqlx
Does sqlx's compile-time query checking prevent SQL injection?
query! checks a literal query against a database schema at build time; it does not retroactively make a dynamically assembled query safe. Keep values separate from SQL with bind parameters.
let user_id: String = read_user_input();
let q = format!(
"SELECT id FROM users WHERE id = '{}'",
user_id
);
// Representative only: data and SQL syntax are mixed.
// Never pass user-influenced SQL as AssertSqlSafe.
let user_id: &str = read_user_input();
// PostgreSQL; query! checks the configured schema.
sqlx::query!(
"SELECT id FROM users WHERE id = $1",
user_id
).fetch_one(&pool).await?
What the compile-time check actually validates
SQLx 0.9's query! macro requires a string literal and checks syntax, bind types, and result types against the configured database schema at build time; configure DATABASE_URL or checked-in offline metadata. This code pair is specifically PostgreSQL: its placeholder is $1. SQLx recommends $1 for PostgreSQL and SQLite; MySQL and MariaDB use positional ? placeholders. Runtime query() can bind values with .bind() when the SQL structure is fixed or application-controlled. In SQLx 0.9, passing a dynamic string requires AssertSqlSafe; that type is only an assertion, not a sanitizer. Never use it to bless a string containing untrusted interpolated values.
How to check your codebase
rg -n 'format!\s*\(|sqlx::query(_as)?\s*\(' --glob '*.rs'FAQ
Use query! when its literal-query and build-time schema requirements fit your project; it adds compile-time SQL and type checking. Dynamic query() is also safe when you bind every value with .bind(). In both cases, never interpolate untrusted data into SQL syntax.
References
- flawopen.com — SQL Injection in Rust
- SQLx docs — query! macro and backend placeholders
- SQLx docs — AssertSqlSafe (assertion, not sanitization)