Skip to content

Commit 57c1dcf

Browse files
committed
tweak: per-crate loaded .env vars storage
1 parent 0c731ae commit 57c1dcf

File tree

1 file changed

+79
-56
lines changed
  • sqlx-macros-core/src/query

1 file changed

+79
-56
lines changed

sqlx-macros-core/src/query/mod.rs

Lines changed: 79 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
#[cfg(procmacro2_semver_exempt)]
2-
use std::collections::HashSet;
1+
use std::cell::RefCell;
32
use std::collections::{hash_map, HashMap};
43
use std::env::VarError;
5-
use std::hash::{BuildHasherDefault, DefaultHasher};
64
use std::path::{Path, PathBuf};
75
use std::sync::{Arc, LazyLock, Mutex};
86
use std::{fs, io};
@@ -113,15 +111,20 @@ impl Metadata {
113111
}
114112
}
115113

116-
static METADATA: LazyLock<Mutex<HashMap<String, Metadata>>> = LazyLock::new(Default::default);
114+
static METADATA: LazyLock<Mutex<HashMap<PathBuf, Arc<Metadata>>>> = LazyLock::new(Default::default);
115+
static CRATE_ENV_FILE_VARS: LazyLock<Mutex<HashMap<PathBuf, HashMap<String, String>>>> =
116+
LazyLock::new(Default::default);
117+
118+
thread_local! {
119+
static CURRENT_CRATE_MANIFEST_DIR: RefCell<PathBuf> = RefCell::new(PathBuf::new());
120+
}
117121

118122
// If we are in a workspace, lookup `workspace_root` since `CARGO_MANIFEST_DIR` won't
119123
// reflect the workspace dir: https://github.com/rust-lang/cargo/issues/3946
120-
fn init_metadata(manifest_dir: &String) -> crate::Result<Metadata> {
121-
let manifest_dir: PathBuf = manifest_dir.into();
124+
fn init_metadata(manifest_dir: &Path) -> crate::Result<Arc<Metadata>> {
122125
let config = Config::try_from_crate_or_default()?;
123126

124-
load_env(&manifest_dir, &config);
127+
load_env(manifest_dir, &config);
125128

126129
let offline = env("SQLX_OFFLINE")
127130
.map(|s| s.eq_ignore_ascii_case("true") || s == "1")
@@ -131,40 +134,41 @@ fn init_metadata(manifest_dir: &String) -> crate::Result<Metadata> {
131134

132135
let database_url = env(config.common.database_url_var()).ok();
133136

134-
Ok(Metadata {
135-
manifest_dir,
137+
Ok(Arc::new(Metadata {
138+
manifest_dir: manifest_dir.to_path_buf(),
136139
offline,
137140
database_url,
138141
offline_dir,
139142
config,
140143
workspace_root: Arc::new(Mutex::new(None)),
141-
})
144+
}))
142145
}
143146

144147
pub fn expand_input<'a>(
145148
input: QueryMacroInput,
146149
drivers: impl IntoIterator<Item = &'a QueryDriver>,
147150
) -> crate::Result<TokenStream> {
148-
let manifest_dir = env("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` must be set");
151+
// `CARGO_MANIFEST_DIR` can only be loaded from a real environment variable due to the filtering done
152+
// by `load_env`, so the value of `CURRENT_CRATE_MANIFEST_DIR` does not matter here.
153+
let manifest_dir =
154+
PathBuf::from(env("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` must be set"));
155+
CURRENT_CRATE_MANIFEST_DIR.set(manifest_dir.clone());
149156

150-
let mut metadata_lock = METADATA
151-
.lock()
152-
// Just reset the metadata on error
153-
.unwrap_or_else(|poison_err| {
154-
let mut guard = poison_err.into_inner();
155-
*guard = Default::default();
156-
guard
157-
});
157+
let mut metadata_lock = METADATA.lock().unwrap();
158158

159159
let metadata = match metadata_lock.entry(manifest_dir) {
160-
hash_map::Entry::Occupied(occupied) => occupied.into_mut(),
160+
hash_map::Entry::Occupied(occupied) => Arc::clone(&occupied.get()),
161161
hash_map::Entry::Vacant(vacant) => {
162162
let metadata = init_metadata(vacant.key())?;
163-
vacant.insert(metadata)
163+
vacant.insert(Arc::clone(&metadata));
164+
metadata
164165
}
165166
};
166167

167-
let data_source = match &metadata {
168+
// Release the lock now so other expansions in other threads of this process can proceed concurrently.
169+
drop(metadata_lock);
170+
171+
let data_source = match &*metadata {
168172
Metadata {
169173
offline: false,
170174
database_url: Some(db_url),
@@ -182,7 +186,7 @@ pub fn expand_input<'a>(
182186
];
183187
let Some(data_file_path) = dirs
184188
.iter()
185-
.filter_map(|path| path(metadata))
189+
.filter_map(|path| path(&metadata))
186190
.map(|path| path.join(&filename))
187191
.find(|path| path.exists())
188192
else {
@@ -416,10 +420,11 @@ where
416420
Ok(ret_tokens)
417421
}
418422

419-
static LOADED_ENV_VARS: Mutex<HashMap<String, String, BuildHasherDefault<DefaultHasher>>> =
420-
Mutex::new(HashMap::with_hasher(BuildHasherDefault::new()));
421-
422-
/// Get the value of an environment variable, telling the compiler about it if applicable.
423+
/// Get the value of an environment variable for the current crate, telling the compiler about it if applicable.
424+
///
425+
/// The current crate is determined by the `CURRENT_CRATE_MANIFEST_DIR` thread-local variable, which is assumed
426+
/// to be set to match the crate whose macro is being expanded before this function is called. It is also assumed
427+
/// that the expansion of this macro happens on a single thread.
423428
fn env(name: &str) -> Result<String, std::env::VarError> {
424429
#[cfg(procmacro2_semver_exempt)]
425430
let tracked_value = Some(proc_macro::tracked_env::var(name));
@@ -428,26 +433,37 @@ fn env(name: &str) -> Result<String, std::env::VarError> {
428433

429434
match tracked_value.map_or_else(|| std::env::var(name), |var| var) {
430435
Ok(v) => Ok(v),
431-
Err(VarError::NotPresent) => LOADED_ENV_VARS
432-
.lock()
433-
.unwrap()
434-
.get(name)
435-
.cloned()
436+
Err(VarError::NotPresent) => CURRENT_CRATE_MANIFEST_DIR
437+
.with_borrow(|manifest_dir| {
438+
CRATE_ENV_FILE_VARS
439+
.lock()
440+
.unwrap()
441+
.get(manifest_dir)
442+
.cloned()
443+
})
444+
.and_then(|env_file_vars| env_file_vars.get(name).cloned())
436445
.ok_or(VarError::NotPresent),
437446
Err(e) => Err(e),
438447
}
439448
}
440449

441-
/// Load configuration environment variables from a `.env` file, without overriding existing
442-
/// environment variables. If applicable, the compiler is informed about the loaded env vars
443-
/// and the `.env` files they may come from.
450+
/// Load configuration environment variables from a `.env` file. If applicable, the compiler is
451+
/// about the `.env` files they may come from.
444452
fn load_env(manifest_dir: &Path, config: &Config) {
445-
let loadable_vars = [
446-
"DATABASE_URL",
447-
"SQLX_OFFLINE",
448-
"SQLX_OFFLINE_DIR",
449-
config.common.database_url_var(),
450-
];
453+
// A whitelist of environment variables to load from a `.env` file avoids
454+
// such files overriding internal variables they should not (e.g., `CARGO`,
455+
// `CARGO_MANIFEST_DIR`) when using the `env` function above.
456+
let database_url_var = config.common.database_url_var();
457+
let loadable_vars = if database_url_var == "DATABASE_URL" {
458+
&["DATABASE_URL", "SQLX_OFFLINE", "SQLX_OFFLINE_DIR"][..]
459+
} else {
460+
&[
461+
"DATABASE_URL",
462+
"SQLX_OFFLINE",
463+
"SQLX_OFFLINE_DIR",
464+
database_url_var,
465+
]
466+
};
451467

452468
let (found_dotenv, candidate_dotenv_paths) = find_dotenv(manifest_dir);
453469

@@ -461,26 +477,33 @@ fn load_env(manifest_dir: &Path, config: &Config) {
461477
}
462478
}
463479

464-
if let Some(dotenv_path) = found_dotenv
480+
let loaded_vars = found_dotenv
465481
.then_some(candidate_dotenv_paths)
466482
.iter()
467483
.flatten()
468484
.last()
469-
{
470-
for dotenv_var_result in dotenvy::from_path_iter(dotenv_path)
471-
.ok()
472-
.into_iter()
473-
.flatten()
474-
{
475-
let Ok((key, value)) = dotenv_var_result else {
476-
continue;
477-
};
485+
.map(|dotenv_path| {
486+
dotenvy::from_path_iter(dotenv_path)
487+
.ok()
488+
.into_iter()
489+
.flatten()
490+
.filter_map(|dotenv_var_result| match dotenv_var_result {
491+
Ok((key, value))
492+
if loadable_vars.contains(&&*key) && std::env::var(&key).is_err() =>
493+
{
494+
Some((key, value))
495+
}
496+
_ => None,
497+
})
498+
})
499+
.into_iter()
500+
.flatten()
501+
.collect();
478502

479-
if loadable_vars.contains(&&*key) && std::env::var(&key).is_err() {
480-
LOADED_ENV_VARS.lock().unwrap().insert(key, value);
481-
}
482-
}
483-
}
503+
CRATE_ENV_FILE_VARS
504+
.lock()
505+
.unwrap()
506+
.insert(manifest_dir.to_path_buf(), loaded_vars);
484507
}
485508

486509
fn find_dotenv(mut dir: &Path) -> (bool, Vec<PathBuf>) {

0 commit comments

Comments
 (0)