From 43f11648234a4c4090b663dafb1ea27901eda7ad Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Fri, 3 Jan 2020 19:02:31 -0800 Subject: [PATCH 1/2] fix macro examples and tests --- examples/queries/account-by-id.sql | 7 +- src/lib.rs | 256 +---------------------------- src/macros.rs | 252 ++++++++++++++++++++++++++++ tests/mysql.rs | 3 +- 4 files changed, 258 insertions(+), 260 deletions(-) create mode 100644 src/macros.rs diff --git a/examples/queries/account-by-id.sql b/examples/queries/account-by-id.sql index fe100fb5..98623eb8 100644 --- a/examples/queries/account-by-id.sql +++ b/examples/queries/account-by-id.sql @@ -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 = ? diff --git a/src/lib.rs b/src/lib.rs index a5475520..589e2b67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 = ; -/// 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 = ; -/// 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 = ; -/// 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 = ; -/// 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 = ; -/// 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)*) - }) -); diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..afbef614 --- /dev/null +++ b/src/macros.rs @@ -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 = ; +/// 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 = ; +/// 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 = ; +/// 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 = ; +/// 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 = ; +/// 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)*) + }) +); diff --git a/tests/mysql.rs b/tests/mysql.rs index 140d4138..a53526a7 100644 --- a/tests/mysql.rs +++ b/tests/mysql.rs @@ -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) From 589d06ec66cd89ccdc9114157cf1546d500f36ab Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Fri, 3 Jan 2020 19:06:00 -0800 Subject: [PATCH 2/2] change `UnexpectedEof` to `ConnectionAborted` --- sqlx-core/src/mysql/connection.rs | 2 +- sqlx-core/src/postgres/executor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/mysql/connection.rs b/sqlx-core/src/mysql/connection.rs index b5900425..f7c92f30 100644 --- a/sqlx-core/src/mysql/connection.rs +++ b/sqlx-core/src/mysql/connection.rs @@ -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) } diff --git a/sqlx-core/src/postgres/executor.rs b/sqlx-core/src/postgres/executor.rs index 9a20dbf9..7c4f678e 100644 --- a/sqlx-core/src/postgres/executor.rs +++ b/sqlx-core/src/postgres/executor.rs @@ -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()) } }