Skip to content

Commit 218ff5f

Browse files
authored
docs(macros): document that bind parameter nullability is not compile-checked (#4345)
Restructures the "Nullability: Bind Parameters" section of the query! macro documentation to state upfront that the nullability of bind parameters is not verified at compile time, why (SQLx does not parse SQL), and give the concrete example from #2642. The previous phrasing understated the limitation and hid it behind guidance about WHERE clauses, which is now demoted to a sub-section. Closes #2642
1 parent 54ee68b commit 218ff5f

1 file changed

Lines changed: 29 additions & 5 deletions

File tree

‎src/macros/mod.rs‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,42 @@
130130
/// * MySQL/SQLite: `?` which matches arguments in order that it appears in the query
131131
///
132132
/// ## Nullability: Bind Parameters
133-
/// For a given expected type `T`, both `T` and `Option<T>` are allowed (as well as either
134-
/// behind references). `Option::None` will be bound as `NULL`, so if binding a type behind `Option`
135-
/// be sure your query can support it.
133+
/// **The nullability of bind parameters is _not_ verified at compile time.** Unlike output
134+
/// columns (see the [next section](#nullability-output-columns)), the `query!()` family of
135+
/// macros does not check whether an `Option<T>` bound to a parameter is compatible with the
136+
/// nullability of the target column. This is a fundamental limitation, not an oversight:
137+
/// determining which parameter maps to which column would require the macros to parse and
138+
/// analyze the SQL themselves, which SQLx explicitly does not do
139+
/// (see [the FAQ][faq-parse-sql] for the reasoning).
140+
///
141+
/// For any bind parameter, both `T` and `Option<T>` are accepted (as well as either behind
142+
/// references). `Option::None` is bound as SQL `NULL`. If the target column has a `NOT NULL`
143+
/// constraint, binding `None` will compile successfully but fail **at runtime** with a
144+
/// database error, for example:
136145
///
137-
/// Note, however, if binding in a `where` clause, that equality comparisons with `NULL` may not
138-
/// work as expected; instead you must use `IS NOT NULL` or `IS NULL` to check if a column is not
146+
/// ```rust,ignore
147+
/// // Schema: `CREATE TABLE foo (data TEXT NOT NULL);`
148+
/// // Compiles fine, fails at runtime:
149+
/// sqlx::query!("INSERT INTO foo (data) VALUES ($1)", None::<String>)
150+
/// .execute(&pool)
151+
/// .await?;
152+
/// ```
153+
///
154+
/// If you need this kind of safety, encode the constraint in your Rust types (e.g. use `String`
155+
/// instead of `Option<String>` for the field that feeds the bind parameter) or cover the
156+
/// invariant with an integration test against a real database.
157+
///
158+
/// ### Bind parameters in `WHERE` clauses
159+
/// If binding in a `WHERE` clause, note that equality comparisons with `NULL` may not work
160+
/// as expected; instead you must use `IS NOT NULL` or `IS NULL` to check if a column is not
139161
/// null or is null, respectively.
140162
///
141163
/// In Postgres and MySQL you may also use `IS [NOT] DISTINCT FROM` to compare with a possibly
142164
/// `NULL` value. In MySQL `IS NOT DISTINCT FROM` can be shortened to `<=>`.
143165
/// In SQLite you can use `IS` or `IS NOT`. Note that operator precedence may be different.
144166
///
167+
/// [faq-parse-sql]: https://github.com/transact-rs/sqlx/blob/main/FAQ.md#why-cant-sqlx-just-look-at-my-database-schemamigrations-and-parse-the-sql-itself
168+
///
145169
/// ## Nullability: Output Columns
146170
/// In most cases, the database engine can tell us whether or not a column may be `NULL`, and
147171
/// the `query!()` macro adjusts the field types of the returned struct accordingly.

0 commit comments

Comments
 (0)