Skip to content

Commit edb6b5f

Browse files
committed
fix(macros): tell the compiler about external files/env vars to watch
closes #663 closes #681
1 parent be189bd commit edb6b5f

File tree

9 files changed

+201
-31
lines changed

9 files changed

+201
-31
lines changed

sqlx-macros/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
not(any(feature = "postgres", feature = "mysql", feature = "offline")),
33
allow(dead_code, unused_macros, unused_imports)
44
)]
5+
#![cfg_attr(procmacro2_semver_exempt, feature(track_path, proc_macro_tracked_env))]
56
extern crate proc_macro;
67

78
use proc_macro::TokenStream;

sqlx-macros/src/migrate.rs

+31-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct QuotedMigration {
2424
version: i64,
2525
description: String,
2626
migration_type: QuotedMigrationType,
27-
sql: String,
27+
path: String,
2828
checksum: Vec<u8>,
2929
}
3030

@@ -34,7 +34,7 @@ impl ToTokens for QuotedMigration {
3434
version,
3535
description,
3636
migration_type,
37-
sql,
37+
path,
3838
checksum,
3939
} = &self;
4040

@@ -43,7 +43,8 @@ impl ToTokens for QuotedMigration {
4343
version: #version,
4444
description: ::std::borrow::Cow::Borrowed(#description),
4545
migration_type: #migration_type,
46-
sql: ::std::borrow::Cow::Borrowed(#sql),
46+
// this tells the compiler to watch this path for changes
47+
sql: ::std::borrow::Cow::Borrowed(include_str!(#path)),
4748
checksum: ::std::borrow::Cow::Borrowed(&[
4849
#(#checksum),*
4950
]),
@@ -59,7 +60,7 @@ pub(crate) fn expand_migrator_from_dir(dir: LitStr) -> crate::Result<TokenStream
5960
let path = crate::common::resolve_path(&dir.value(), dir.span())?;
6061
let mut migrations = Vec::new();
6162

62-
for entry in fs::read_dir(path)? {
63+
for entry in fs::read_dir(&path)? {
6364
let entry = entry?;
6465
if !fs::metadata(entry.path())?.is_file() {
6566
// not a file; ignore
@@ -89,18 +90,43 @@ pub(crate) fn expand_migrator_from_dir(dir: LitStr) -> crate::Result<TokenStream
8990

9091
let checksum = Vec::from(Sha384::digest(sql.as_bytes()).as_slice());
9192

93+
// canonicalize the path so we can pass it to `include_str!()`
94+
let path = entry.path().canonicalize()?;
95+
let path = path
96+
.to_str()
97+
.ok_or_else(|| {
98+
format!(
99+
"migration path cannot be represented as a string: {:?}",
100+
path
101+
)
102+
})?
103+
.to_owned();
104+
92105
migrations.push(QuotedMigration {
93106
version,
94107
description,
95108
migration_type: QuotedMigrationType(migration_type),
96-
sql,
109+
path,
97110
checksum,
98111
})
99112
}
100113

101114
// ensure that we are sorted by `VERSION ASC`
102115
migrations.sort_by_key(|m| m.version);
103116

117+
#[cfg(procmacro2_semver_exempt)]
118+
{
119+
let path = path.canonicalize()?;
120+
let path = path.to_str().ok_or_else(|| {
121+
format!(
122+
"migration directory path cannot be represented as a string: {:?}",
123+
path
124+
)
125+
})?;
126+
127+
proc_macro::tracked_path::path(path);
128+
}
129+
104130
Ok(quote! {
105131
::sqlx::migrate::Migrator {
106132
migrations: ::std::borrow::Cow::Borrowed(&[

sqlx-macros/src/query/data.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,30 @@ pub mod offline {
6161
/// Find and deserialize the data table for this query from a shared `sqlx-data.json`
6262
/// file. The expected structure is a JSON map keyed by the SHA-256 hash of queries in hex.
6363
pub fn from_data_file(path: impl AsRef<Path>, query: &str) -> crate::Result<Self> {
64-
serde_json::Deserializer::from_reader(BufReader::new(
64+
let this = serde_json::Deserializer::from_reader(BufReader::new(
6565
File::open(path.as_ref()).map_err(|e| {
6666
format!("failed to open path {}: {}", path.as_ref().display(), e)
6767
})?,
6868
))
6969
.deserialize_map(DataFileVisitor {
7070
query,
7171
hash: hash_string(query),
72-
})
73-
.map_err(Into::into)
72+
})?;
73+
74+
#[cfg(procmacr2_semver_exempt)]
75+
{
76+
let path = path.as_ref().canonicalize()?;
77+
let path = path.to_str().ok_or_else(|| {
78+
format!(
79+
"sqlx-data.json path cannot be represented as a string: {:?}",
80+
path
81+
)
82+
})?;
83+
84+
proc_macro::tracked_path::path(path);
85+
}
86+
87+
Ok(this)
7488
}
7589
}
7690

sqlx-macros/src/query/input.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use syn::{ExprArray, Type};
88

99
/// Macro input shared by `query!()` and `query_file!()`
1010
pub struct QueryMacroInput {
11-
pub(super) src: String,
11+
pub(super) sql: String,
1212

1313
#[cfg_attr(not(feature = "offline"), allow(dead_code))]
1414
pub(super) src_span: Span,
@@ -18,6 +18,8 @@ pub struct QueryMacroInput {
1818
pub(super) arg_exprs: Vec<Expr>,
1919

2020
pub(super) checked: bool,
21+
22+
pub(super) file_path: Option<String>,
2123
}
2224

2325
enum QuerySrc {
@@ -94,12 +96,15 @@ impl Parse for QueryMacroInput {
9496

9597
let arg_exprs = args.unwrap_or_default();
9698

99+
let file_path = src.file_path(src_span)?;
100+
97101
Ok(QueryMacroInput {
98-
src: src.resolve(src_span)?,
102+
sql: src.resolve(src_span)?,
99103
src_span,
100104
record_type,
101105
arg_exprs,
102106
checked,
107+
file_path,
103108
})
104109
}
105110
}
@@ -112,6 +117,27 @@ impl QuerySrc {
112117
QuerySrc::File(file) => read_file_src(&file, source_span),
113118
}
114119
}
120+
121+
fn file_path(&self, source_span: Span) -> syn::Result<Option<String>> {
122+
if let QuerySrc::File(ref file) = *self {
123+
let path = std::path::Path::new(file)
124+
.canonicalize()
125+
.map_err(|e| syn::Error::new(source_span, e))?;
126+
127+
Ok(Some(
128+
path.to_str()
129+
.ok_or_else(|| {
130+
syn::Error::new(
131+
source_span,
132+
"query file path cannot be represented as a string",
133+
)
134+
})?
135+
.to_string(),
136+
))
137+
} else {
138+
Ok(None)
139+
}
140+
}
115141
}
116142

117143
fn read_file_src(source: &str, source_span: Span) -> syn::Result<String> {

sqlx-macros/src/query/mod.rs

+37-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod input;
2323
mod output;
2424

2525
struct Metadata {
26+
#[allow(unused)]
2627
manifest_dir: PathBuf,
2728
offline: bool,
2829
database_url: Option<String>,
@@ -35,42 +36,47 @@ struct Metadata {
3536
// If we are in a workspace, lookup `workspace_root` since `CARGO_MANIFEST_DIR` won't
3637
// reflect the workspace dir: https://github.com/rust-lang/cargo/issues/3946
3738
static METADATA: Lazy<Metadata> = Lazy::new(|| {
38-
use std::env;
39-
40-
let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")
39+
let manifest_dir: PathBuf = env("CARGO_MANIFEST_DIR")
4140
.expect("`CARGO_MANIFEST_DIR` must be set")
4241
.into();
4342

4443
#[cfg(feature = "offline")]
45-
let target_dir =
46-
env::var_os("CARGO_TARGET_DIR").map_or_else(|| "target".into(), |dir| dir.into());
44+
let target_dir = env("CARGO_TARGET_DIR").map_or_else(|_| "target".into(), |dir| dir.into());
4745

4846
// If a .env file exists at CARGO_MANIFEST_DIR, load environment variables from this,
4947
// otherwise fallback to default dotenv behaviour.
5048
let env_path = manifest_dir.join(".env");
51-
if env_path.exists() {
49+
50+
#[cfg_attr(not(procmacro2_semver_exempt), allow(unused_variables))]
51+
let env_path = if env_path.exists() {
5252
let res = dotenv::from_path(&env_path);
5353
if let Err(e) = res {
5454
panic!("failed to load environment from {:?}, {}", env_path, e);
5555
}
56+
57+
Some(env_path)
5658
} else {
57-
let _ = dotenv::dotenv();
59+
dotenv::dotenv().ok()
60+
};
61+
62+
// tell the compiler to watch the `.env` for changes, if applicable
63+
#[cfg(procmacro2_semver_exempt)]
64+
if let Some(env_path) = env_path.as_ref().and_then(|path| path.to_str()) {
65+
proc_macro::tracked_path::path(env_path);
5866
}
5967

60-
// TODO: Switch to `var_os` after feature(osstring_ascii) is stable.
61-
// Stabilization PR: https://github.com/rust-lang/rust/pull/80193
62-
let offline = env::var("SQLX_OFFLINE")
68+
let offline = env("SQLX_OFFLINE")
6369
.map(|s| s.eq_ignore_ascii_case("true") || s == "1")
6470
.unwrap_or(false);
6571

66-
let database_url = env::var("DATABASE_URL").ok();
72+
let database_url = env("DATABASE_URL").ok();
6773

6874
#[cfg(feature = "offline")]
6975
let workspace_root = {
7076
use serde::Deserialize;
7177
use std::process::Command;
7278

73-
let cargo = env::var_os("CARGO").expect("`CARGO` must be set");
79+
let cargo = env("CARGO").expect("`CARGO` must be set");
7480

7581
let output = Command::new(&cargo)
7682
.args(&["metadata", "--format-version=1"])
@@ -151,7 +157,7 @@ fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result<TokenSt
151157
"postgres" | "postgresql" => {
152158
let data = block_on(async {
153159
let mut conn = sqlx_core::postgres::PgConnection::connect(db_url.as_str()).await?;
154-
QueryData::from_db(&mut conn, &input.src).await
160+
QueryData::from_db(&mut conn, &input.sql).await
155161
})?;
156162

157163
expand_with_data(input, data, false)
@@ -164,7 +170,7 @@ fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result<TokenSt
164170
"mssql" | "sqlserver" => {
165171
let data = block_on(async {
166172
let mut conn = sqlx_core::mssql::MssqlConnection::connect(db_url.as_str()).await?;
167-
QueryData::from_db(&mut conn, &input.src).await
173+
QueryData::from_db(&mut conn, &input.sql).await
168174
})?;
169175

170176
expand_with_data(input, data, false)
@@ -177,7 +183,7 @@ fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result<TokenSt
177183
"mysql" | "mariadb" => {
178184
let data = block_on(async {
179185
let mut conn = sqlx_core::mysql::MySqlConnection::connect(db_url.as_str()).await?;
180-
QueryData::from_db(&mut conn, &input.src).await
186+
QueryData::from_db(&mut conn, &input.sql).await
181187
})?;
182188

183189
expand_with_data(input, data, false)
@@ -190,7 +196,7 @@ fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result<TokenSt
190196
"sqlite" => {
191197
let data = block_on(async {
192198
let mut conn = sqlx_core::sqlite::SqliteConnection::connect(db_url.as_str()).await?;
193-
QueryData::from_db(&mut conn, &input.src).await
199+
QueryData::from_db(&mut conn, &input.sql).await
194200
})?;
195201

196202
expand_with_data(input, data, false)
@@ -207,7 +213,7 @@ fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result<TokenSt
207213
pub fn expand_from_file(input: QueryMacroInput, file: PathBuf) -> crate::Result<TokenStream> {
208214
use data::offline::DynQueryData;
209215

210-
let query_data = DynQueryData::from_data_file(file, &input.src)?;
216+
let query_data = DynQueryData::from_data_file(file, &input.sql)?;
211217
assert!(!query_data.db_name.is_empty());
212218

213219
match &*query_data.db_name {
@@ -288,7 +294,7 @@ where
288294
.all(|it| it.type_info().is_void())
289295
{
290296
let db_path = DB::db_path();
291-
let sql = &input.src;
297+
let sql = &input.sql;
292298

293299
quote! {
294300
::sqlx::query_with::<#db_path, _>(#sql, #query_args)
@@ -368,3 +374,16 @@ where
368374

369375
Ok(ret_tokens)
370376
}
377+
378+
/// Get the value of an environment variable, telling the compiler about it if applicable.
379+
fn env(name: &str) -> Result<String, std::env::VarError> {
380+
#[cfg(procmacro2_semver_exempt)]
381+
{
382+
proc_macro::tracked_env::var(name)
383+
}
384+
385+
#[cfg(not(procmacro2_semver_exempt))]
386+
{
387+
std::env::var(name)
388+
}
389+
}

sqlx-macros/src/query/output.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,14 @@ pub fn quote_query_as<DB: DatabaseExt>(
157157

158158
let db_path = DB::db_path();
159159
let row_path = DB::row_path();
160-
let sql = &input.src;
160+
161+
// if this query came from a file, use `include_str!()` to tell the compiler where it came from
162+
let sql = if let Some(ref path) = &input.file_path {
163+
quote::quote_spanned! { input.src_span => include_str!(#path) }
164+
} else {
165+
let sql = &input.sql;
166+
quote! { #sql }
167+
};
161168

162169
quote! {
163170
::sqlx::query_with::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| {
@@ -200,7 +207,7 @@ pub fn quote_query_scalar<DB: DatabaseExt>(
200207
};
201208

202209
let db = DB::db_path();
203-
let query = &input.src;
210+
let query = &input.sql;
204211

205212
Ok(quote! {
206213
::sqlx::query_scalar_with::<#db, #ty, _>(#query, #bind_args)

0 commit comments

Comments
 (0)