Austin Bonander 388c424f48
fix(macros): smarter .env loading, caching, and invalidation (#4053)
* fix(macros): smarter `.env` loading, caching, and invalidation

* feat(mysql): test `.env` loading in CI

* feat(postgres): test `.env` loading in CI

* feat(macros): allow `DATABASE_URL` to be empty

* fix(examples/postgres): make `cargo-sqlx` executable

* fix(examples/postgres): `cargo sqlx` invocation

* feat(examples/postgres): check offline prepare on more examples

* fix(examples/postgres): the name of this step

* fix(cli): don't suppress error from `dotenv()`

* fix(ci/examples/postgres): don't use heredoc in this step

* fix(ci/examples/postgres): multi-tenant

* fix(ci/examples/sqlite): test `.env` loading

* chore: add CHANGELOG entry
2025-10-14 17:31:12 -07:00

98 lines
2.4 KiB
Rust

use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::SystemTime;
/// A cached value derived from one or more files, which is automatically invalidated
/// if the modified-time of any watched file changes.
pub struct MtimeCache<T> {
inner: Mutex<Option<MtimeCacheInner<T>>>,
}
pub struct MtimeCacheBuilder {
file_mtimes: Vec<(PathBuf, Option<SystemTime>)>,
}
struct MtimeCacheInner<T> {
builder: MtimeCacheBuilder,
cached: T,
}
impl<T: Clone> MtimeCache<T> {
pub fn new() -> Self {
MtimeCache {
inner: Mutex::new(None),
}
}
/// Get the cached value, or (re)initialize it if it does not exist or a file's mtime has changed.
pub fn get_or_try_init<E>(
&self,
init: impl FnOnce(&mut MtimeCacheBuilder) -> Result<T, E>,
) -> Result<T, E> {
let mut inner = self.inner.lock().unwrap_or_else(|e| {
// Reset the cache on-panic.
let mut locked = e.into_inner();
*locked = None;
locked
});
if let Some(inner) = &*inner {
if !inner.builder.any_modified() {
return Ok(inner.cached.clone());
}
}
let mut builder = MtimeCacheBuilder::new();
let value = init(&mut builder)?;
*inner = Some(MtimeCacheInner {
builder,
cached: value.clone(),
});
Ok(value)
}
}
impl MtimeCacheBuilder {
fn new() -> Self {
MtimeCacheBuilder {
file_mtimes: Vec::new(),
}
}
/// Add a file path to watch.
///
/// The cached value will be automatically invalidated if the modified-time of the file changes,
/// or if the file does not exist but is created sometime after this call.
pub fn add_path(&mut self, path: PathBuf) {
let mtime = get_mtime(&path);
#[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))]
{
proc_macro::tracked_path::path(&path);
}
self.file_mtimes.push((path, mtime));
}
fn any_modified(&self) -> bool {
for (path, expected_mtime) in &self.file_mtimes {
let actual_mtime = get_mtime(path);
if expected_mtime != &actual_mtime {
return true;
}
}
false
}
}
fn get_mtime(path: &Path) -> Option<SystemTime> {
std::fs::metadata(path)
.and_then(|metadata| metadata.modified())
.ok()
}