Skip to content

Commit 9b496da

Browse files
committed
WIP add support for building in "decoupled" mode
1 parent c285e28 commit 9b496da

File tree

15 files changed

+581
-470
lines changed

15 files changed

+581
-470
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ default = [ "macros", "runtime-async-std" ]
3939
macros = [ "sqlx-macros" ]
4040
tls = [ "sqlx-core/tls" ]
4141

42+
# offline building support in `sqlx-macros`
43+
offline = ["sqlx-macros/offline"]
44+
4245
# intended mainly for CI and docs
4346
all = [ "tls", "all-database", "all-type" ]
4447
all-database = [ "mysql", "sqlite", "postgres" ]

sqlx-macros/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ proc-macro = true
1919
default = [ "runtime-async-std" ]
2020

2121
runtime-async-std = [ "sqlx/runtime-async-std", "async-std" ]
22-
runtime-tokio = [ "sqlx/runtime-tokio", "tokio", "lazy_static" ]
22+
runtime-tokio = [ "sqlx/runtime-tokio", "tokio", "once_cell" ]
23+
24+
# offline building support
25+
offline = ["serde", "serde_json", "hex", "sha2"]
2326

2427
# database
2528
mysql = [ "sqlx/mysql" ]
@@ -39,11 +42,14 @@ async-std = { version = "1.5.0", default-features = false, optional = true }
3942
tokio = { version = "0.2.13", default-features = false, features = [ "rt-threaded" ], optional = true }
4043
dotenv = { version = "0.15.0", default-features = false }
4144
futures = { version = "0.3.4", default-features = false, features = [ "executor" ] }
45+
hex = { version = "0.4.2", optional = true }
4246
heck = "0.3"
4347
proc-macro2 = { version = "1.0.9", default-features = false }
4448
sqlx = { version = "0.3.4", default-features = false, path = "../sqlx-core", package = "sqlx-core" }
49+
serde = { version = "1.0", optional = true }
4550
serde_json = { version = "1.0", features = [ "raw_value" ], optional = true }
51+
sha2 = { verison = "0.8.1", optional = true }
4652
syn = { version = "1.0.16", default-features = false, features = [ "full" ] }
4753
quote = { version = "1.0.2", default-features = false }
4854
url = { version = "2.1.1", default-features = false }
49-
lazy_static = { version = "1.4.0", optional = true }
55+
once_cell = { version = "1.3", features = ["std"], optional = true }

sqlx-macros/src/database/mod.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,7 @@
11
use sqlx::database::Database;
22

3-
#[derive(PartialEq, Eq)]
4-
#[allow(dead_code)]
5-
pub enum ParamChecking {
6-
Strong,
7-
Weak,
8-
}
9-
103
pub trait DatabaseExt: Database {
11-
const DATABASE_PATH: &'static str;
12-
const ROW_PATH: &'static str;
13-
14-
const PARAM_CHECKING: ParamChecking;
15-
16-
fn db_path() -> syn::Path {
17-
syn::parse_str(Self::DATABASE_PATH).unwrap()
18-
}
19-
20-
fn row_path() -> syn::Path {
21-
syn::parse_str(Self::ROW_PATH).unwrap()
22-
}
23-
4+
// db_path() and row_path() and PARAM_CHECKING moved to query_macros::data::Database
245
fn param_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
256

267
fn return_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
@@ -33,15 +14,9 @@ macro_rules! impl_database_ext {
3314
$database:path {
3415
$($(#[$meta:meta])? $ty:ty $(| $input:ty)?),*$(,)?
3516
},
36-
ParamChecking::$param_checking:ident,
3717
feature-types: $name:ident => $get_gate:expr,
38-
row = $row:path
3918
) => {
4019
impl $crate::database::DatabaseExt for $database {
41-
const DATABASE_PATH: &'static str = stringify!($database);
42-
const ROW_PATH: &'static str = stringify!($row);
43-
const PARAM_CHECKING: $crate::database::ParamChecking = $crate::database::ParamChecking::$param_checking;
44-
4520
fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
4621
match () {
4722
$(

sqlx-macros/src/database/mysql.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,5 @@ impl_database_ext! {
4444
#[cfg(feature = "bigdecimal")]
4545
sqlx::types::BigDecimal,
4646
},
47-
ParamChecking::Weak,
4847
feature-types: info => info.type_feature_gate(),
49-
row = sqlx::mysql::MySqlRow
5048
}

sqlx-macros/src/database/postgres.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,5 @@ impl_database_ext! {
9595
Vec<sqlx::types::ipnetwork::IpNetwork> | &[sqlx::types::ipnetwork::IpNetwork],
9696

9797
},
98-
ParamChecking::Strong,
9998
feature-types: info => info.type_feature_gate(),
100-
row = sqlx::postgres::PgRow
10199
}

sqlx-macros/src/database/sqlite.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,5 @@ impl_database_ext! {
88
String,
99
Vec<u8>,
1010
},
11-
ParamChecking::Weak,
1211
feature-types: _info => None,
13-
row = sqlx::sqlite::SqliteRow
1412
}

sqlx-macros/src/lib.rs

Lines changed: 12 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ use quote::quote;
1111
#[cfg(feature = "runtime-async-std")]
1212
use async_std::task::block_on;
1313

14-
use std::path::PathBuf;
15-
1614
use url::Url;
1715

1816
type Error = Box<dyn std::error::Error>;
@@ -26,23 +24,6 @@ mod runtime;
2624

2725
use query_macros::*;
2826

29-
#[cfg(feature = "runtime-tokio")]
30-
lazy_static::lazy_static! {
31-
static ref BASIC_RUNTIME: tokio::runtime::Runtime = {
32-
tokio::runtime::Builder::new()
33-
.threaded_scheduler()
34-
.enable_io()
35-
.enable_time()
36-
.build()
37-
.expect("failed to build tokio runtime")
38-
};
39-
}
40-
41-
#[cfg(feature = "runtime-tokio")]
42-
fn block_on<F: std::future::Future>(future: F) -> F::Output {
43-
BASIC_RUNTIME.enter(|| futures::executor::block_on(future))
44-
}
45-
4627
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
4728
quote!(
4829
macro_rules! macro_result {
@@ -52,141 +33,21 @@ fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
5233
.into()
5334
}
5435

55-
macro_rules! async_macro (
56-
($db:ident, $input:ident: $ty:ty => $expr:expr) => {{
57-
let $input = match syn::parse::<$ty>($input) {
58-
Ok(input) => input,
59-
Err(e) => return macro_result(e.to_compile_error()),
60-
};
61-
62-
let res: Result<proc_macro2::TokenStream> = block_on(async {
63-
use sqlx::connection::Connect;
64-
65-
// If a .env file exists at CARGO_MANIFEST_DIR, load environment variables from this,
66-
// otherwise fallback to default dotenv behaviour.
67-
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
68-
let env_path = PathBuf::from(dir).join(".env");
69-
if env_path.exists() {
70-
dotenv::from_path(&env_path)
71-
.map_err(|e| format!("failed to load environment from {:?}, {}", env_path, e))?
72-
}
73-
}
36+
#[proc_macro]
37+
pub fn expand_query(input: TokenStream) -> TokenStream {
38+
let input = syn::parse_macro_input!(input as QueryMacroInput);
7439

75-
let db_url = Url::parse(&dotenv::var("DATABASE_URL").map_err(|_| "DATABASE_URL not set")?)?;
76-
77-
match db_url.scheme() {
78-
#[cfg(feature = "sqlite")]
79-
"sqlite" => {
80-
let $db = sqlx::sqlite::SqliteConnection::connect(db_url.as_str())
81-
.await
82-
.map_err(|e| format!("failed to connect to database: {}", e))?;
83-
84-
$expr.await
85-
}
86-
#[cfg(not(feature = "sqlite"))]
87-
"sqlite" => Err(format!(
88-
"DATABASE_URL {} has the scheme of a SQLite database but the `sqlite` \
89-
feature of sqlx was not enabled",
90-
db_url
91-
).into()),
92-
#[cfg(feature = "postgres")]
93-
"postgresql" | "postgres" => {
94-
let $db = sqlx::postgres::PgConnection::connect(db_url.as_str())
95-
.await
96-
.map_err(|e| format!("failed to connect to database: {}", e))?;
97-
98-
$expr.await
99-
}
100-
#[cfg(not(feature = "postgres"))]
101-
"postgresql" | "postgres" => Err(format!(
102-
"DATABASE_URL {} has the scheme of a Postgres database but the `postgres` \
103-
feature of sqlx was not enabled",
104-
db_url
105-
).into()),
106-
#[cfg(feature = "mysql")]
107-
"mysql" | "mariadb" => {
108-
let $db = sqlx::mysql::MySqlConnection::connect(db_url.as_str())
109-
.await
110-
.map_err(|e| format!("failed to connect to database: {}", e))?;
111-
112-
$expr.await
113-
}
114-
#[cfg(not(feature = "mysql"))]
115-
"mysql" | "mariadb" => Err(format!(
116-
"DATABASE_URL {} has the scheme of a MySQL/MariaDB database but the `mysql` \
117-
feature of sqlx was not enabled",
118-
db_url
119-
).into()),
120-
scheme => Err(format!("unexpected scheme {:?} in DATABASE_URL {}", scheme, db_url).into()),
121-
}
122-
});
123-
124-
match res {
125-
Ok(ts) => ts.into(),
126-
Err(e) => {
127-
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
128-
macro_result(parse_err.to_compile_error())
129-
} else {
130-
let msg = e.to_string();
131-
macro_result(quote!(compile_error!(#msg)))
132-
}
40+
match query_macros::expand_input(input) {
41+
Ok(ts) => ts.into(),
42+
Err(e) => {
43+
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
44+
macro_result(parse_err.to_compile_error())
45+
} else {
46+
let msg = e.to_string();
47+
macro_result(quote!(compile_error!(#msg)))
13348
}
13449
}
135-
}}
136-
);
137-
138-
#[proc_macro]
139-
#[allow(unused_variables)]
140-
pub fn query(input: TokenStream) -> TokenStream {
141-
#[allow(unused_variables)]
142-
async_macro!(db, input: QueryMacroInput => expand_query(input, db, true))
143-
}
144-
145-
#[proc_macro]
146-
#[allow(unused_variables)]
147-
pub fn query_unchecked(input: TokenStream) -> TokenStream {
148-
#[allow(unused_variables)]
149-
async_macro!(db, input: QueryMacroInput => expand_query(input, db, false))
150-
}
151-
152-
#[proc_macro]
153-
#[allow(unused_variables)]
154-
pub fn query_file(input: TokenStream) -> TokenStream {
155-
#[allow(unused_variables)]
156-
async_macro!(db, input: QueryMacroInput => expand_query_file(input, db, true))
157-
}
158-
159-
#[proc_macro]
160-
#[allow(unused_variables)]
161-
pub fn query_file_unchecked(input: TokenStream) -> TokenStream {
162-
#[allow(unused_variables)]
163-
async_macro!(db, input: QueryMacroInput => expand_query_file(input, db, false))
164-
}
165-
166-
#[proc_macro]
167-
#[allow(unused_variables)]
168-
pub fn query_as(input: TokenStream) -> TokenStream {
169-
#[allow(unused_variables)]
170-
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, true))
171-
}
172-
173-
#[proc_macro]
174-
#[allow(unused_variables)]
175-
pub fn query_file_as(input: TokenStream) -> TokenStream {
176-
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, true))
177-
}
178-
179-
#[proc_macro]
180-
#[allow(unused_variables)]
181-
pub fn query_as_unchecked(input: TokenStream) -> TokenStream {
182-
#[allow(unused_variables)]
183-
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, false))
184-
}
185-
186-
#[proc_macro]
187-
#[allow(unused_variables)]
188-
pub fn query_file_as_unchecked(input: TokenStream) -> TokenStream {
189-
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, false))
50+
}
19051
}
19152

19253
#[proc_macro_derive(Encode, attributes(sqlx))]

sqlx-macros/src/query_macros/args.rs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ use syn::Expr;
55
use quote::{quote, quote_spanned, ToTokens};
66
use sqlx::describe::Describe;
77

8-
use crate::database::{DatabaseExt, ParamChecking};
9-
use crate::query_macros::QueryMacroInput;
8+
use super::data::QueryData;
9+
use super::QueryMacroInput;
10+
use crate::database::DatabaseExt;
1011

1112
/// Returns a tokenstream which typechecks the arguments passed to the macro
1213
/// and binds them to `DB::Arguments` with the ident `query_args`.
13-
pub fn quote_args<DB: DatabaseExt>(
14-
input: &QueryMacroInput,
15-
describe: &Describe<DB>,
16-
checked: bool,
17-
) -> crate::Result<TokenStream> {
18-
let db_path = DB::db_path();
14+
pub fn quote_args(input: &QueryMacroInput, data: &QueryData) -> crate::Result<TokenStream> {
15+
let db_path = data.db.path();
1916

2017
if input.arg_names.is_empty() {
2118
return Ok(quote! {
@@ -25,24 +22,16 @@ pub fn quote_args<DB: DatabaseExt>(
2522

2623
let arg_name = &input.arg_names;
2724

28-
let args_check = if checked && DB::PARAM_CHECKING == ParamChecking::Strong {
29-
describe
30-
.param_types
25+
let args_check = if input.checked && data.db.strong_param_checking() {
26+
data
27+
.input_types
3128
.iter()
3229
.zip(input.arg_names.iter().zip(&input.arg_exprs))
3330
.enumerate()
3431
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> {
35-
// TODO: We could remove the ParamChecking flag and just filter to only test params that are non-null
36-
let param_ty = param_ty.as_ref().unwrap();
37-
3832
let param_ty = get_type_override(expr)
39-
.or_else(|| {
40-
Some(
41-
DB::param_type_for_id(&param_ty)?
42-
.parse::<proc_macro2::TokenStream>()
43-
.unwrap(),
44-
)
45-
})
33+
// TODO: We could remove the ParamChecking flag and just filter to only test params that are non-null
34+
.or_else(|| Some(param_ty.as_deref()?.parse().unwrap()))
4635
.ok_or_else(|| {
4736
if let Some(feature_gate) = <DB as DatabaseExt>::get_feature_gate(&param_ty) {
4837
format!(

0 commit comments

Comments
 (0)