diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 0db6f0c2e7..ef9b6a3a2e 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -130,18 +130,42 @@ /// * MySQL/SQLite: `?` which matches arguments in order that it appears in the query /// /// ## Nullability: Bind Parameters -/// For a given expected type `T`, both `T` and `Option` are allowed (as well as either -/// behind references). `Option::None` will be bound as `NULL`, so if binding a type behind `Option` -/// be sure your query can support it. +/// **The nullability of bind parameters is _not_ verified at compile time.** Unlike output +/// columns (see the [next section](#nullability-output-columns)), the `query!()` family of +/// macros does not check whether an `Option` bound to a parameter is compatible with the +/// nullability of the target column. This is a fundamental limitation, not an oversight: +/// determining which parameter maps to which column would require the macros to parse and +/// analyze the SQL themselves, which SQLx explicitly does not do +/// (see [the FAQ][faq-parse-sql] for the reasoning). +/// +/// For any bind parameter, both `T` and `Option` are accepted (as well as either behind +/// references). `Option::None` is bound as SQL `NULL`. If the target column has a `NOT NULL` +/// constraint, binding `None` will compile successfully but fail **at runtime** with a +/// database error, for example: /// -/// Note, however, if binding in a `where` clause, that equality comparisons with `NULL` may not -/// work as expected; instead you must use `IS NOT NULL` or `IS NULL` to check if a column is not +/// ```rust,ignore +/// // Schema: `CREATE TABLE foo (data TEXT NOT NULL);` +/// // Compiles fine, fails at runtime: +/// sqlx::query!("INSERT INTO foo (data) VALUES ($1)", None::) +/// .execute(&pool) +/// .await?; +/// ``` +/// +/// If you need this kind of safety, encode the constraint in your Rust types (e.g. use `String` +/// instead of `Option` for the field that feeds the bind parameter) or cover the +/// invariant with an integration test against a real database. +/// +/// ### Bind parameters in `WHERE` clauses +/// If binding in a `WHERE` clause, note that equality comparisons with `NULL` may not work +/// as expected; instead you must use `IS NOT NULL` or `IS NULL` to check if a column is not /// null or is null, respectively. /// /// In Postgres and MySQL you may also use `IS [NOT] DISTINCT FROM` to compare with a possibly /// `NULL` value. In MySQL `IS NOT DISTINCT FROM` can be shortened to `<=>`. /// In SQLite you can use `IS` or `IS NOT`. Note that operator precedence may be different. /// +/// [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 +/// /// ## Nullability: Output Columns /// In most cases, the database engine can tell us whether or not a column may be `NULL`, and /// the `query!()` macro adjusts the field types of the returned struct accordingly.