mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-09-28 13:31:41 +00:00
strip out proc-macro-hack
This commit is contained in:
parent
28ed854b03
commit
da5c538d22
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1308,7 +1308,6 @@ dependencies = [
|
||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sqlx-core 0.1.4",
|
||||
"sqlx-macros 0.1.1",
|
||||
]
|
||||
@ -1368,7 +1367,6 @@ dependencies = [
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sqlx-core 0.1.4",
|
||||
|
@ -29,7 +29,7 @@ all-features = true
|
||||
|
||||
[features]
|
||||
default = [ "macros" ]
|
||||
macros = [ "sqlx-macros", "proc-macro-hack" ]
|
||||
macros = [ "sqlx-macros" ]
|
||||
tls = ["sqlx-core/tls"]
|
||||
|
||||
# database
|
||||
@ -43,7 +43,6 @@ uuid = [ "sqlx-core/uuid", "sqlx-macros/uuid" ]
|
||||
[dependencies]
|
||||
sqlx-core = { version = "=0.1.4", path = "sqlx-core" }
|
||||
sqlx-macros = { version = "0.1.1", path = "sqlx-macros", optional = true }
|
||||
proc-macro-hack = { version = "0.5.11", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.26"
|
||||
|
@ -33,7 +33,6 @@ uuid = [ "sqlx/uuid" ]
|
||||
async-std = { version = "1.4.0", default-features = false }
|
||||
dotenv = { version = "0.15.0", default-features = false }
|
||||
futures = { version = "0.3.1", default-features = false }
|
||||
proc-macro-hack = { version = "0.5.11", default-features = false }
|
||||
proc-macro2 = { version = "1.0.6", default-features = false }
|
||||
sqlx = { version = "0.1.1", default-features = false, path = "../sqlx-core", package = "sqlx-core" }
|
||||
syn = { version = "1.0.11", default-features = false, features = [ "full" ] }
|
||||
|
@ -6,8 +6,6 @@ extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use proc_macro_hack::proc_macro_hack;
|
||||
|
||||
use quote::quote;
|
||||
|
||||
use syn::parse_macro_input;
|
||||
@ -26,8 +24,22 @@ mod query_macros;
|
||||
|
||||
use query_macros::*;
|
||||
|
||||
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
|
||||
quote!(
|
||||
macro_rules! macro_result {
|
||||
($($args:tt)*) => (#tokens)
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
macro_rules! async_macro (
|
||||
($db:ident => $expr:expr) => {{
|
||||
($db:ident, $input:ident: $ty:ty => $expr:expr) => {{
|
||||
let $input = match syn::parse::<$ty>($input) {
|
||||
Ok(input) => input,
|
||||
Err(e) => return macro_result(e.to_compile_error()),
|
||||
};
|
||||
|
||||
let res: Result<proc_macro2::TokenStream> = task::block_on(async {
|
||||
use sqlx::Connection;
|
||||
|
||||
@ -70,40 +82,36 @@ macro_rules! async_macro (
|
||||
Ok(ts) => ts.into(),
|
||||
Err(e) => {
|
||||
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
|
||||
return dbg!(parse_err).to_compile_error().into();
|
||||
macro_result(parse_err.to_compile_error())
|
||||
} else {
|
||||
let msg = format!("{:?}", e);
|
||||
macro_result(quote!(compile_error(#msg)))
|
||||
}
|
||||
|
||||
let msg = format!("{:?}", e);
|
||||
quote!(compile_error!(#msg);).into()
|
||||
}
|
||||
}
|
||||
}}
|
||||
);
|
||||
|
||||
#[proc_macro_hack]
|
||||
#[proc_macro]
|
||||
pub fn query(input: TokenStream) -> TokenStream {
|
||||
#[allow(unused_variables)]
|
||||
let input = parse_macro_input!(input as QueryMacroInput);
|
||||
async_macro!(db => expand_query(input, db))
|
||||
async_macro!(db, input: QueryMacroInput => expand_query(input, db))
|
||||
}
|
||||
|
||||
#[proc_macro_hack]
|
||||
#[proc_macro]
|
||||
pub fn query_file(input: TokenStream) -> TokenStream {
|
||||
#[allow(unused_variables)]
|
||||
let input = parse_macro_input!(input as QueryMacroInput);
|
||||
async_macro!(db => expand_query_file(input, db))
|
||||
async_macro!(db, input: QueryMacroInput => expand_query_file(input, db))
|
||||
}
|
||||
|
||||
#[proc_macro_hack]
|
||||
#[proc_macro]
|
||||
pub fn query_as(input: TokenStream) -> TokenStream {
|
||||
#[allow(unused_variables)]
|
||||
let input = parse_macro_input!(input as QueryAsMacroInput);
|
||||
async_macro!(db => expand_query_as(input, db))
|
||||
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db))
|
||||
}
|
||||
|
||||
#[proc_macro_hack]
|
||||
#[proc_macro]
|
||||
pub fn query_file_as(input: TokenStream) -> TokenStream {
|
||||
#[allow(unused_variables)]
|
||||
let input = parse_macro_input!(input as QueryAsMacroInput);
|
||||
async_macro!(db => expand_query_file_as(input, db))
|
||||
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db))
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
input: &QueryMacroInput,
|
||||
describe: &Describe<DB>,
|
||||
) -> crate::Result<TokenStream> {
|
||||
if input.args.is_empty() {
|
||||
if input.arg_names.is_empty() {
|
||||
return Ok(quote! {
|
||||
let args = ();
|
||||
});
|
||||
@ -22,7 +22,7 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
let param_types = describe
|
||||
.param_types
|
||||
.iter()
|
||||
.zip(&*input.args)
|
||||
.zip(&*input.arg_exprs)
|
||||
.map(|(type_, expr)| {
|
||||
get_type_override(expr)
|
||||
.or_else(|| {
|
||||
@ -36,7 +36,7 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
})
|
||||
.collect::<crate::Result<Vec<_>>>()?;
|
||||
|
||||
let args_ty_cons = input.args.iter().enumerate().map(|(i, expr)| {
|
||||
let args_ty_cons = input.arg_names.iter().enumerate().map(|(i, expr)| {
|
||||
// required or `quote!()` emits it as `Nusize`
|
||||
let i = syn::Index::from(i);
|
||||
quote_spanned!( expr.span() => {
|
||||
@ -56,10 +56,10 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
TokenStream::new()
|
||||
};
|
||||
|
||||
let args = input.args.iter();
|
||||
let args = input.arg_names.iter();
|
||||
|
||||
Ok(quote! {
|
||||
let args = (#(&#args),*,);
|
||||
let args = (#(&$#args),*,);
|
||||
#args_check
|
||||
})
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
use std::env;
|
||||
|
||||
use async_std::fs;
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::Token;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Group;
|
||||
use syn::{Expr, ExprLit, ExprPath, Lit};
|
||||
use syn::{ExprGroup, Token};
|
||||
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
|
||||
use sqlx::describe::Describe;
|
||||
use sqlx::Connection;
|
||||
@ -14,28 +18,55 @@ use sqlx::Connection;
|
||||
pub struct QueryMacroInput {
|
||||
pub(super) source: String,
|
||||
pub(super) source_span: Span,
|
||||
pub(super) args: Vec<Expr>,
|
||||
// `arg0 .. argN` for N arguments
|
||||
pub(super) arg_names: Vec<Ident>,
|
||||
pub(super) arg_exprs: Vec<Expr>,
|
||||
}
|
||||
|
||||
impl QueryMacroInput {
|
||||
fn from_exprs(input: ParseStream, mut args: impl Iterator<Item = Expr>) -> syn::Result<Self> {
|
||||
let sql = match args.next() {
|
||||
fn lit_err<T>(span: Span, unexpected: Expr) -> syn::Result<T> {
|
||||
Err(syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"expected string literal, got {}",
|
||||
unexpected.to_token_stream()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
let (source, source_span) = match args.next() {
|
||||
Some(Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(sql), ..
|
||||
})) => sql,
|
||||
Some(other_expr) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
other_expr,
|
||||
"expected string literal",
|
||||
));
|
||||
})) => (sql.value(), sql.span()),
|
||||
Some(Expr::Group(ExprGroup {
|
||||
expr,
|
||||
group_token: Group { span },
|
||||
..
|
||||
})) => {
|
||||
// this duplication with the above is necessary because `expr` is `Box<Expr>` here
|
||||
// which we can't directly pattern-match without `box_patterns`
|
||||
match *expr {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(sql), ..
|
||||
}) => (sql.value(), span),
|
||||
other_expr => return lit_err(span, other_expr),
|
||||
}
|
||||
}
|
||||
Some(other_expr) => return lit_err(other_expr.span(), other_expr),
|
||||
None => return Err(input.error("expected SQL string literal")),
|
||||
};
|
||||
|
||||
let arg_exprs: Vec<_> = args.collect();
|
||||
let arg_names = (0..arg_exprs.len())
|
||||
.map(|i| format_ident!("arg{}", i))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
source: sql.value(),
|
||||
source_span: sql.span(),
|
||||
args: args.collect(),
|
||||
source,
|
||||
source_span,
|
||||
arg_exprs,
|
||||
arg_names,
|
||||
})
|
||||
}
|
||||
|
||||
@ -56,13 +87,13 @@ impl QueryMacroInput {
|
||||
.await
|
||||
.map_err(|e| syn::Error::new(self.source_span, e))?;
|
||||
|
||||
if self.args.len() != describe.param_types.len() {
|
||||
if self.arg_names.len() != describe.param_types.len() {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
format!(
|
||||
"expected {} parameters, got {}",
|
||||
describe.param_types.len(),
|
||||
self.args.len()
|
||||
self.arg_names.len()
|
||||
),
|
||||
)
|
||||
.into());
|
||||
@ -97,16 +128,33 @@ impl QueryAsMacroInput {
|
||||
|
||||
impl Parse for QueryAsMacroInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
fn path_err<T>(span: Span, unexpected: Expr) -> syn::Result<T> {
|
||||
Err(syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"expected path to a type, got {}",
|
||||
unexpected.to_token_stream()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
let mut args = Punctuated::<Expr, Token![,]>::parse_terminated(input)?.into_iter();
|
||||
|
||||
let as_ty = match args.next() {
|
||||
Some(Expr::Path(path)) => path,
|
||||
Some(other_expr) => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
other_expr,
|
||||
"expected path to a type",
|
||||
));
|
||||
Some(Expr::Group(ExprGroup {
|
||||
expr,
|
||||
group_token: Group { span },
|
||||
..
|
||||
})) => {
|
||||
// this duplication with the above is necessary because `expr` is `Box<Expr>` here
|
||||
// which we can't directly pattern-match without `box_patterns`
|
||||
match *expr {
|
||||
Expr::Path(path) => path,
|
||||
other_expr => return path_err(span, other_expr),
|
||||
}
|
||||
}
|
||||
Some(other_expr) => return path_err(other_expr.span(), other_expr),
|
||||
None => return Err(input.error("expected path to SQL file")),
|
||||
};
|
||||
|
||||
|
@ -46,6 +46,7 @@ where
|
||||
}
|
||||
|
||||
let args_tokens = args::quote_args(&input.query_input, &describe)?;
|
||||
let arg_names = &input.query_input.arg_names;
|
||||
|
||||
let columns = output::columns_to_rust(&describe)?;
|
||||
let output = output::quote_query_as::<C::Database>(
|
||||
@ -54,10 +55,14 @@ where
|
||||
&columns,
|
||||
);
|
||||
|
||||
Ok(quote! {{
|
||||
#args_tokens
|
||||
#output.bind_all(args)
|
||||
}})
|
||||
Ok(quote! {
|
||||
macro_rules! macro_result {
|
||||
(#($#arg_names:expr),*) => {{
|
||||
#args_tokens
|
||||
#output.bind_all(args)
|
||||
}}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn expand_query_file_as<C: Connection>(
|
||||
|
@ -50,15 +50,20 @@ where
|
||||
.collect::<TokenStream>();
|
||||
|
||||
let output = output::quote_query_as::<C::Database>(sql, &record_type, &columns);
|
||||
let arg_names = &input.arg_names;
|
||||
|
||||
Ok(quote! {{
|
||||
#[derive(Debug)]
|
||||
struct #record_type {
|
||||
#record_fields
|
||||
}
|
||||
Ok(quote! {
|
||||
macro_rules! macro_result {
|
||||
(#($#arg_names:expr),*) => {{
|
||||
#[derive(Debug)]
|
||||
struct #record_type {
|
||||
#record_fields
|
||||
}
|
||||
|
||||
#args
|
||||
#args
|
||||
|
||||
#output.bind_all(args)
|
||||
}})
|
||||
#output.bind_all(args)
|
||||
}
|
||||
}}
|
||||
})
|
||||
}
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -20,34 +20,14 @@ pub use sqlx_core::mysql::{self, MySql, MySqlConnection, MySqlPool};
|
||||
#[cfg(feature = "postgres")]
|
||||
pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres};
|
||||
|
||||
#[allow(unused_attributes)]
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
pub extern crate sqlx_macros;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[macro_export]
|
||||
mod macros;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
|
||||
#[allow(dead_code)]
|
||||
pub use sqlx_macros::query as query_;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
|
||||
#[allow(dead_code)]
|
||||
pub use sqlx_macros::query_as as query_as_;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
|
||||
#[allow(dead_code)]
|
||||
pub use sqlx_macros::query_file as query_file_;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
|
||||
#[allow(dead_code)]
|
||||
pub use sqlx_macros::query_file_as as query_file_as_;
|
||||
|
||||
// macro support
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
|
@ -82,18 +82,23 @@
|
||||
/// * [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)
|
||||
// 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:tt)*) => (#[allow(dead_code)]{
|
||||
#![allow(dead_code)]
|
||||
$crate::query_!($query, $($args)*)
|
||||
($query:literal, $($args:expr),*) => ({
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::query!($query, $($args),*);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
})
|
||||
);
|
||||
|
||||
@ -139,14 +144,21 @@ macro_rules! query (
|
||||
/// # #[cfg(not(feature = "mysql"))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[cfg(feature = "macros")]
|
||||
#[macro_export]
|
||||
macro_rules! query_file (
|
||||
($query:literal) => (#[allow(dead_code)]{
|
||||
$crate::query_file_!($query)
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::query_file!($query);
|
||||
}
|
||||
macro_result!()
|
||||
});
|
||||
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
|
||||
$crate::query_file_!($query, $($args)*)
|
||||
($query:literal, $($args:expr),*) => (#[allow(dead_code)]{
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::query_file!($query, $($args),*);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
})
|
||||
);
|
||||
|
||||
@ -197,14 +209,21 @@ macro_rules! query_file (
|
||||
/// # #[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)
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::query_as!($out_struct, $query);
|
||||
}
|
||||
macro_result!()
|
||||
});
|
||||
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] {
|
||||
$crate::query_as_!($out_struct, $query, $($args)*)
|
||||
($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),*)
|
||||
})
|
||||
);
|
||||
|
||||
@ -240,13 +259,20 @@ macro_rules! query_as (
|
||||
/// # #[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)
|
||||
#[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)] {
|
||||
$crate::query_file_as_!($out_struct, $query, $($args)*)
|
||||
($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::query_file_as!($out_struct, $query, $($args),*);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
})
|
||||
);
|
||||
|
@ -73,6 +73,21 @@ async fn pool_immediately_fails_with_db_error() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "macros")]
|
||||
#[async_std::test]
|
||||
async fn macro_select_from_cte() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
let account =
|
||||
sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name) accounts")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
println!("{:?}", account);
|
||||
println!("{}: {}", account.id, account.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[async_std::test]
|
||||
async fn macro_select_from_cte_bind() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
let account = sqlx::query!(
|
||||
"select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",
|
||||
|
Loading…
x
Reference in New Issue
Block a user