Merge pull request #23 from launchbadge/ab/macros-doc-fixes

fix macro examples and tests for MySQL
This commit is contained in:
Ryan Leckey 2020-01-03 21:54:48 -08:00 committed by GitHub
commit ba4521e236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 260 additions and 262 deletions

View File

@ -1,5 +1,2 @@
with accounts (id, name) as (
VALUES (1, 'Herp Derpinson')
)
select * from accounts
where id = ?;
select * from (select (1) as id, 'Herp Derpinson' as name) accounts
where id = ?

View File

@ -133,7 +133,7 @@ impl MySqlConnection {
pub(crate) async fn receive(&mut self) -> crate::Result<&mut Self> {
self.try_receive()
.await?
.ok_or(io::ErrorKind::UnexpectedEof)?;
.ok_or(io::ErrorKind::ConnectionAborted)?;
Ok(self)
}

View File

@ -125,7 +125,7 @@ impl super::PgConnection {
}
// Connection was (unexpectedly) closed
Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
Err(io::Error::from(io::ErrorKind::ConnectionAborted).into())
}
}

View File

@ -20,6 +20,9 @@ pub use sqlx_core::mysql::{self, MySql, MySqlConnection, MySqlPool};
#[cfg(feature = "postgres")]
pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres};
#[macro_export]
mod macros;
#[cfg(feature = "macros")]
#[doc(hidden)]
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
@ -52,256 +55,3 @@ pub mod ty_cons;
#[cfg(feature = "macros")]
#[doc(hidden)]
pub mod result_ext;
/// 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
/// # #[cfg(feature = "mysql")]
/// # #[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::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(not(feature = "mysql"))]
/// # 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
/// # #[cfg(feature = "mysql")]
/// # #[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::connect(db_url).await?;
/// // let mut conn = <impl sqlx::Executor>;
/// let account = sqlx::query!(
/// "with accounts (id, name) as (VALUES (1, 'Herp Derpinson')) \
/// select * from accounts where id = ?",
/// 1i32
/// )
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # 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
///
/// ## 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.
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query (
// the emitted item for `#[proc_macro_hack]` doesn't look great in docs
// plus this might let IDEs hint at the syntax
// `#[allow(dead_code)]` to silence the `enum ProcMacroHack` error
($query:literal) => (#[allow(dead_code)] {
$crate::query_!($query)
});
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
#![allow(dead_code)]
$crate::query_!($query, $($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.
///
/// -----
///
/// `queries/account-by-id.sql`:
/// ```sql
/// SELECT * from (VALUES(1, 'Herp Derpinson')) accounts(id, name) where id != ?;
/// ```
///
/// `src/my_query.rs`:
/// ```rust
/// # #[cfg(feature = "mysql")]
/// # #[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::connect(db_url).await?;
/// // let mut conn = <impl sqlx::Executor>;
/// let account = sqlx::query_file!("examples/queries/account-by-id.sql", 1i32)
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
///
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file (
($query:literal) => (#[allow(dead_code)]{
$crate::query_file_!($query)
});
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
$crate::query_file_!($query, $($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
/// # #[cfg(feature = "mysql")]
/// # #[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::connect(db_url).await?;
/// #[derive(Debug)]
/// struct Account {
/// id: i32,
/// name: String
/// }
///
/// // let mut conn = <impl sqlx::Executor>;
/// let account = sqlx::query_as!(
/// Account,
/// "with accounts (id, name) as (VALUES (1, 'Herp Derpinson')) \
/// select * from accounts where id = ?",
/// 1i32
/// )
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
///
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query)
});
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query, $($args)*)
})
);
/// Combines the syntaxes of [query_as!] and [query_file!].
///
/// Enforces requirements of both macros; see them for details.
///
/// ```rust
/// # #[cfg(feature = "mysql")]
/// # #[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::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, "examples/queries/account-by-id.sql", 1i32)
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
///
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query)
});
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query, $($args)*)
})
);

252
src/macros.rs Normal file
View File

@ -0,0 +1,252 @@
/// 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
/// # #[cfg(feature = "mysql")]
/// # #[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::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(not(feature = "mysql"))]
/// # 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
/// # #[cfg(feature = "mysql")]
/// # #[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::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(not(feature = "mysql"))]
/// # 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
///
/// ## 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.
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query (
// the emitted item for `#[proc_macro_hack]` doesn't look great in docs
// plus this might let IDEs hint at the syntax
// `#[allow(dead_code)]` to silence the `enum ProcMacroHack` error
($query:literal) => (#[allow(dead_code)] {
$crate::query_!($query)
});
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
#![allow(dead_code)]
$crate::query_!($query, $($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`:
/// ```sql
/// select * from (select (1) as id, 'Herp Derpinson' as name) accounts
/// where id = ?
/// ```
///
/// `src/my_query.rs`:
/// ```rust
/// # #[cfg(feature = "mysql")]
/// # #[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::connect(db_url).await?;
/// // let mut conn = <impl sqlx::Executor>;
/// let account = sqlx::query_file!("examples/queries/account-by-id.sql", 1i32)
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
///
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file (
($query:literal) => (#[allow(dead_code)]{
$crate::query_file_!($query)
});
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
$crate::query_file_!($query, $($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
/// # #[cfg(feature = "mysql")]
/// # #[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::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(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query)
});
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query, $($args)*)
})
);
/// Combines the syntaxes of [query_as!] and [query_file!].
///
/// Enforces requirements of both macros; see them for details.
///
/// ```rust
/// # #[cfg(feature = "mysql")]
/// # #[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::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, "examples/queries/account-by-id.sql", 1i32)
/// .fetch_one(&mut conn)
/// .await?;
///
/// println!("{:?}", account);
/// println!("{}: {}", account.id, account.name);
///
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query)
});
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query, $($args)*)
})
);

View File

@ -53,8 +53,7 @@ CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY)
async fn macro_select_from_cte() -> anyhow::Result<()> {
let mut conn = connect().await?;
let account = sqlx::query!(
"with accounts (id, name) as (VALUES (1, 'Herp Derpinson')) \
select * from accounts where id = ?",
"select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",
1i32
)
.fetch_one(&mut conn)