mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-30 13:20:59 +00:00
410 lines
15 KiB
Rust
410 lines
15 KiB
Rust
/// Statically checked SQL query with `println!()` style syntax.
|
|
///
|
|
/// This expands to an instance of [QueryAs] that outputs an ad-hoc anonymous struct type,
|
|
/// if the query has output columns, or `()` (unit) otherwise:
|
|
///
|
|
/// ```rust
|
|
/// # use sqlx::Connect;
|
|
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
|
/// # #[async_std::main]
|
|
/// # async fn main() -> sqlx::Result<()>{
|
|
/// # let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
/// #
|
|
/// # if !(db_url.starts_with("mysql") || db_url.starts_with("mariadb")) { return Ok(()) }
|
|
/// # let mut conn = sqlx::MySqlConnection::connect(db_url).await?;
|
|
/// // let mut conn = <impl sqlx::Executor>;
|
|
/// let account = sqlx::query!("select (1) as id, 'Herp Derpinson' as name")
|
|
/// .fetch_one(&mut conn)
|
|
/// .await?;
|
|
///
|
|
/// // anonymous struct has `#[derive(Debug)]` for convenience
|
|
/// println!("{:?}", account);
|
|
/// println!("{}: {}", account.id, account.name);
|
|
///
|
|
/// # Ok(())
|
|
/// # }
|
|
/// #
|
|
/// # #[cfg(any(not(feature = "mysql"), not(feature = "runtime-async-std")))]
|
|
/// # fn main() {}
|
|
/// ```
|
|
///
|
|
/// ## Query Arguments
|
|
/// Like `println!()` and the other formatting macros, you can add bind parameters to your SQL
|
|
/// and this macro will typecheck passed arguments and error on missing ones:
|
|
///
|
|
/// ```rust
|
|
/// # use sqlx::Connect;
|
|
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
|
/// # #[async_std::main]
|
|
/// # async fn main() -> sqlx::Result<()>{
|
|
/// # let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
/// #
|
|
/// # if !(db_url.starts_with("mysql") || db_url.starts_with("mariadb")) { return Ok(()) }
|
|
/// # let mut conn = sqlx::mysql::MySqlConnection::connect(db_url).await?;
|
|
/// // let mut conn = <impl sqlx::Executor>;
|
|
/// let account = sqlx::query!(
|
|
/// // just pretend "accounts" is a real table
|
|
/// "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",
|
|
/// 1i32
|
|
/// )
|
|
/// .fetch_one(&mut conn)
|
|
/// .await?;
|
|
///
|
|
/// println!("{:?}", account);
|
|
/// println!("{}: {}", account.id, account.name);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// #
|
|
/// # #[cfg(any(not(feature = "mysql"), not(feature = "runtime-async-std")))]
|
|
/// # fn main() {}
|
|
/// ```
|
|
///
|
|
/// Bind parameters in the SQL string are specific to the database backend:
|
|
///
|
|
/// * Postgres: `$N` where `N` is the 1-based positional argument index
|
|
/// * MySQL: `?` which matches arguments in order that it appears in the query
|
|
///
|
|
/// ## Nullability: Bind Parameters
|
|
/// For a given expected type `T`, both `T` and `Option<T>` 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.
|
|
///
|
|
/// 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
|
|
/// null or is null, respectively. Note that `IS [NOT] NULL` cannot be bound as a parameter either;
|
|
/// you must modify your query string instead.
|
|
///
|
|
/// ## 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.
|
|
///
|
|
/// For Postgres and SQLite, this only works for columns which come directly from actual tables,
|
|
/// as the implementation will need to query the table metadata to find if a given column
|
|
/// has a `NOT NULL` constraint. Columns that do not have a `NOT NULL` constraint or are the result
|
|
/// of an expression are assumed to be nullable and so `Option<T>` is used instead of `T`.
|
|
///
|
|
/// For MySQL, the implementation looks at [the `NOT_NULL` flag](https://dev.mysql.com/doc/dev/mysql-server/8.0.12/group__group__cs__column__definition__flags.html#ga50377f5ca5b3e92f3931a81fe7b44043)
|
|
/// of [the `ColumnDefinition` structure in `COM_QUERY_OK`](https://dev.mysql.com/doc/internals/en/com-query-response.html#column-definition):
|
|
/// if it is set, `T` is used; if it is not set, `Option<T>` is used.
|
|
///
|
|
/// MySQL appears to be capable of determining the nullability of a result column even if it
|
|
/// is the result of an expression, depending on if the expression may in any case result in
|
|
/// `NULL` which then depends on the semantics of what functions are used. Consult the MySQL
|
|
/// manual for the functions you are using to find the cases in which they return `NULL`.
|
|
///
|
|
/// To override the nullability of an output column, use [query_as!].
|
|
///
|
|
/// ## Requirements
|
|
/// * The `DATABASE_URL` environment variable must be set at build-time to point to a database
|
|
/// server with the schema that the query string will be checked against. (All variants of
|
|
/// `query!()` use [dotenv] so this can be in a `.env` file instead.)
|
|
///
|
|
/// * The query must be a string literal or else it cannot be introspected (and thus cannot
|
|
/// be dynamic or the result of another macro).
|
|
///
|
|
/// * The `QueryAs` instance will be bound to the same database type as `query!()` was compiled
|
|
/// against (e.g. you cannot build against a Postgres database and then run the query against
|
|
/// a MySQL database).
|
|
///
|
|
/// * The schema of the database URL (e.g. `postgres://` or `mysql://`) will be used to
|
|
/// determine the database type.
|
|
///
|
|
/// [dotenv]: https://crates.io/crates/dotenv
|
|
/// ## See Also
|
|
/// * [query_as!] if you want to use a struct you can name,
|
|
/// * [query_file!] if you want to define the SQL query out-of-line,
|
|
/// * [query_file_as!] if you want both of the above.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query (
|
|
// by emitting a macro definition from our proc-macro containing the result tokens,
|
|
// we no longer have a need for `proc-macro-hack`
|
|
($query:literal) => ({
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query!($query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($query:literal, $($args:expr),*$(,)?) => ({
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query!($query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query!] which does not check the input or output types. This still does parse
|
|
/// the query to ensure it's syntactically and semantically valid for the current database.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_unchecked (
|
|
// by emitting a macro definition from our proc-macro containing the result tokens,
|
|
// we no longer have a need for `proc-macro-hack`
|
|
($query:literal) => ({
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_unchecked!($query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($query:literal, $($args:expr),*$(,)?) => ({
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_unchecked!($query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query!] where the SQL query is stored in a separate file.
|
|
///
|
|
/// Useful for large queries and potentially cleaner than multiline strings.
|
|
///
|
|
/// The syntax and requirements (see [query!]) are the same except the SQL string is replaced by a
|
|
/// file path.
|
|
///
|
|
/// The file must be relative to the project root (the directory containing `Cargo.toml`),
|
|
/// unlike `include_str!()` which uses compiler internals to get the path of the file where it
|
|
/// was invoked.
|
|
///
|
|
/// -----
|
|
///
|
|
/// `examples/queries/account-by-id.sql`:
|
|
/// ```text
|
|
/// select * from (select (1) as id, 'Herp Derpinson' as name) accounts
|
|
/// where id = ?
|
|
/// ```
|
|
///
|
|
/// `src/my_query.rs`:
|
|
/// ```rust
|
|
/// # use sqlx::Connect;
|
|
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
|
/// # #[async_std::main]
|
|
/// # async fn main() -> sqlx::Result<()>{
|
|
/// # let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
/// #
|
|
/// # if !(db_url.starts_with("mysql") || db_url.starts_with("mariadb")) { return Ok(()) }
|
|
/// # let mut conn = sqlx::MySqlConnection::connect(db_url).await?;
|
|
/// let account = sqlx::query_file!("tests/test-query-account-by-id.sql", 1i32)
|
|
/// .fetch_one(&mut conn)
|
|
/// .await?;
|
|
///
|
|
/// println!("{:?}", account);
|
|
/// println!("{}: {}", account.id, account.name);
|
|
///
|
|
/// # Ok(())
|
|
/// # }
|
|
/// #
|
|
/// # #[cfg(any(not(feature = "mysql"), not(feature = "runtime-async-std")))]
|
|
/// # fn main() {}
|
|
/// ```
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_file (
|
|
($query:literal) => (#[allow(dead_code)]{
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file!($query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($query:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)]{
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file!($query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query_file!] which does not check the input or output types. This still does parse
|
|
/// the query to ensure it's syntactically and semantically valid for the current database.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_file_unchecked (
|
|
($query:literal) => (#[allow(dead_code)]{
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_unchecked!($query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($query:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)]{
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_unchecked!($query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query!] which takes a path to an explicitly defined struct as the output type.
|
|
///
|
|
/// This lets you return the struct from a function or add your own trait implementations.
|
|
///
|
|
/// No trait implementations are required; the macro maps rows using a struct literal
|
|
/// where the names of columns in the query are expected to be the same as the fields of the struct
|
|
/// (but the order does not need to be the same). The types of the columns are based on the
|
|
/// query and not the corresponding fields of the struct, so this is type-safe as well.
|
|
///
|
|
/// This enforces a few things:
|
|
/// * The query must output at least one column.
|
|
/// * The column names of the query must match the field names of the struct.
|
|
/// * Neither the query nor the struct may have unused fields.
|
|
///
|
|
/// The only modification to the syntax is that the struct name is given before the SQL string:
|
|
/// ```rust
|
|
/// # use sqlx::Connect;
|
|
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
|
/// # #[async_std::main]
|
|
/// # async fn main() -> sqlx::Result<()>{
|
|
/// # let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
/// #
|
|
/// # if !(db_url.starts_with("mysql") || db_url.starts_with("mariadb")) { return Ok(()) }
|
|
/// # let mut conn = sqlx::MySqlConnection::connect(db_url).await?;
|
|
/// #[derive(Debug)]
|
|
/// struct Account {
|
|
/// id: i32,
|
|
/// name: String
|
|
/// }
|
|
///
|
|
/// // let mut conn = <impl sqlx::Executor>;
|
|
/// let account = sqlx::query_as!(
|
|
/// Account,
|
|
/// "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",
|
|
/// 1i32
|
|
/// )
|
|
/// .fetch_one(&mut conn)
|
|
/// .await?;
|
|
///
|
|
/// println!("{:?}", account);
|
|
/// println!("{}: {}", account.id, account.name);
|
|
///
|
|
/// # Ok(())
|
|
/// # }
|
|
/// #
|
|
/// # #[cfg(any(not(feature = "mysql"), not(feature = "runtime-async-std")))]
|
|
/// # fn main() {}
|
|
/// ```
|
|
///
|
|
/// ## Nullability
|
|
/// Use `Option` for columns which may be `NULL` in order to avoid a runtime error being returned
|
|
/// from `.fetch_*()`.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_as (
|
|
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_as!($out_struct, $query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($out_struct:path, $query:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_as!($out_struct, $query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// Combines the syntaxes of [query_as!] and [query_file!].
|
|
///
|
|
/// Enforces requirements of both macros; see them for details.
|
|
///
|
|
/// ```rust
|
|
/// # use sqlx::Connect;
|
|
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
|
/// # #[async_std::main]
|
|
/// # async fn main() -> sqlx::Result<()>{
|
|
/// # let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
/// #
|
|
/// # if !(db_url.starts_with("mysql") || db_url.starts_with("mariadb")) { return Ok(()) }
|
|
/// # let mut conn = sqlx::MySqlConnection::connect(db_url).await?;
|
|
/// #[derive(Debug)]
|
|
/// struct Account {
|
|
/// id: i32,
|
|
/// name: String
|
|
/// }
|
|
///
|
|
/// // let mut conn = <impl sqlx::Executor>;
|
|
/// let account = sqlx::query_file_as!(Account, "tests/test-query-account-by-id.sql", 1i32)
|
|
/// .fetch_one(&mut conn)
|
|
/// .await?;
|
|
///
|
|
/// println!("{:?}", account);
|
|
/// println!("{}: {}", account.id, account.name);
|
|
///
|
|
/// # Ok(())
|
|
/// # }
|
|
/// #
|
|
/// # #[cfg(any(not(feature = "mysql"), not(feature = "runtime-async-std")))]
|
|
/// # fn main() {}
|
|
/// ```
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_file_as (
|
|
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_as!($out_struct, $query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
($out_struct:path, $query:literal, $($args:tt),*$(,)?) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_as!($out_struct, $query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query_as!] which does not check the input or output types. This still does parse
|
|
/// the query to ensure it's syntactically and semantically valid for the current database.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_as_unchecked (
|
|
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_as_unchecked!($out_struct, $query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
|
|
($out_struct:path, $query:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_as_unchecked!($out_struct, $query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|
|
|
|
/// A variant of [query_file_as!] which does not check the input or output types. This
|
|
/// still does parse the query to ensure it's syntactically and semantically valid
|
|
/// for the current database.
|
|
#[macro_export]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
|
macro_rules! query_file_as_unchecked (
|
|
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_as_unchecked!($out_struct, $query);
|
|
}
|
|
macro_result!()
|
|
});
|
|
|
|
($out_struct:path, $query:literal, $($args:tt),*$(,)?) => (#[allow(dead_code)] {
|
|
#[macro_use]
|
|
mod _macro_result {
|
|
$crate::sqlx_macros::query_file_as_unchecked!($out_struct, $query, $($args),*);
|
|
}
|
|
macro_result!($($args),*)
|
|
})
|
|
);
|