#![cfg_attr( not(any(feature = "postgres", feature = "mysql")), allow(dead_code, unused_macros, unused_imports) )] extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; #[cfg(feature = "runtime-async-std")] use async_std::task::block_on; use std::path::PathBuf; use url::Url; type Error = Box; type Result = std::result::Result; mod database; mod derives; mod query_macros; mod runtime; use query_macros::*; #[cfg(feature = "runtime-tokio")] lazy_static::lazy_static! { static ref BASIC_RUNTIME: tokio::runtime::Runtime = { tokio::runtime::Builder::new() .threaded_scheduler() .enable_io() .enable_time() .build() .expect("failed to build tokio runtime") }; } #[cfg(feature = "runtime-tokio")] fn block_on(future: F) -> F::Output { BASIC_RUNTIME.enter(|| futures::executor::block_on(future)) } fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream { quote!( macro_rules! macro_result { ($($args:tt)*) => (#tokens) } ) .into() } macro_rules! async_macro ( ($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 = block_on(async { use sqlx::connection::Connect; // If a .env file exists at CARGO_MANIFEST_DIR, load environment variables from this, // otherwise fallback to default dotenv behaviour. if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { let env_path = PathBuf::from(dir).join(".env"); if env_path.exists() { dotenv::from_path(&env_path) .map_err(|e| format!("failed to load environment from {:?}, {}", env_path, e))? } } let db_url = Url::parse(&dotenv::var("DATABASE_URL").map_err(|_| "DATABASE_URL not set")?)?; match db_url.scheme() { #[cfg(feature = "sqlite")] "sqlite" => { let $db = sqlx::sqlite::SqliteConnection::connect(db_url.as_str()) .await .map_err(|e| format!("failed to connect to database: {}", e))?; $expr.await } #[cfg(not(feature = "sqlite"))] "sqlite" => Err(format!( "DATABASE_URL {} has the scheme of a SQLite database but the `sqlite` \ feature of sqlx was not enabled", db_url ).into()), #[cfg(feature = "postgres")] "postgresql" | "postgres" => { let $db = sqlx::postgres::PgConnection::connect(db_url.as_str()) .await .map_err(|e| format!("failed to connect to database: {}", e))?; $expr.await } #[cfg(not(feature = "postgres"))] "postgresql" | "postgres" => Err(format!( "DATABASE_URL {} has the scheme of a Postgres database but the `postgres` \ feature of sqlx was not enabled", db_url ).into()), #[cfg(feature = "mysql")] "mysql" | "mariadb" => { let $db = sqlx::mysql::MySqlConnection::connect(db_url.as_str()) .await .map_err(|e| format!("failed to connect to database: {}", e))?; $expr.await } #[cfg(not(feature = "mysql"))] "mysql" | "mariadb" => Err(format!( "DATABASE_URL {} has the scheme of a MySQL/MariaDB database but the `mysql` \ feature of sqlx was not enabled", db_url ).into()), scheme => Err(format!("unexpected scheme {:?} in DATABASE_URL {}", scheme, db_url).into()), } }); match res { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { macro_result(parse_err.to_compile_error()) } else { let msg = e.to_string(); macro_result(quote!(compile_error!(#msg))) } } } }} ); #[proc_macro] #[allow(unused_variables)] pub fn query(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryMacroInput => expand_query(input, db, true)) } #[proc_macro] #[allow(unused_variables)] pub fn query_unchecked(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryMacroInput => expand_query(input, db, false)) } #[proc_macro] #[allow(unused_variables)] pub fn query_file(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryMacroInput => expand_query_file(input, db, true)) } #[proc_macro] #[allow(unused_variables)] pub fn query_file_unchecked(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryMacroInput => expand_query_file(input, db, false)) } #[proc_macro] #[allow(unused_variables)] pub fn query_as(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, true)) } #[proc_macro] #[allow(unused_variables)] pub fn query_file_as(input: TokenStream) -> TokenStream { async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, true)) } #[proc_macro] #[allow(unused_variables)] pub fn query_as_unchecked(input: TokenStream) -> TokenStream { #[allow(unused_variables)] async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, false)) } #[proc_macro] #[allow(unused_variables)] pub fn query_file_as_unchecked(input: TokenStream) -> TokenStream { async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, false)) } #[proc_macro_derive(Encode, attributes(sqlx))] pub fn derive_encode(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_encode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[proc_macro_derive(Decode, attributes(sqlx))] pub fn derive_decode(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_decode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[proc_macro_derive(Type, attributes(sqlx))] pub fn derive_type(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_type_encode_decode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[proc_macro_derive(FromRow, attributes(sqlx))] pub fn derive_from_row(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); match derives::expand_derive_from_row(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } }