use std::cell::RefCell;
use smallvec::SmallVec;
use sqry_core::graph::unified::file::id::FileId;
use crate::input::FileInputStore;
pub type FileDep = (FileId, u64);
thread_local! {
static FILE_DEPS: RefCell<Vec<FileId>> = const { RefCell::new(Vec::new()) };
}
pub fn record_file_dep(file_id: FileId) {
if file_id == FileId::INVALID {
return;
}
FILE_DEPS.with(|deps| {
deps.borrow_mut().push(file_id);
});
}
pub struct DependencyRecorderGuard {
_not_send: std::marker::PhantomData<*const ()>,
}
impl DependencyRecorderGuard {
#[must_use]
pub fn new() -> Self {
FILE_DEPS.with(|deps| deps.borrow_mut().clear());
Self {
_not_send: std::marker::PhantomData,
}
}
#[must_use]
pub fn finish(self, inputs: &FileInputStore) -> SmallVec<[FileDep; 8]> {
let raw = FILE_DEPS.with(|deps| std::mem::take(&mut *deps.borrow_mut()));
let mut unique: Vec<FileId> = raw;
unique.sort_unstable();
unique.dedup();
unique
.into_iter()
.filter_map(|fid| inputs.revision(fid).map(|rev| (fid, rev)))
.collect()
}
}
impl Default for DependencyRecorderGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for DependencyRecorderGuard {
fn drop(&mut self) {
FILE_DEPS.with(|deps| deps.borrow_mut().clear());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn guard_clears_on_drop() {
FILE_DEPS.with(|deps| {
deps.borrow_mut().push(FileId::new(1));
deps.borrow_mut().push(FileId::new(2));
});
let guard = DependencyRecorderGuard::new();
FILE_DEPS.with(|deps| {
assert!(deps.borrow().is_empty(), "guard should clear on creation");
});
record_file_dep(FileId::new(10));
record_file_dep(FileId::new(20));
record_file_dep(FileId::new(10));
drop(guard);
FILE_DEPS.with(|deps| {
assert!(deps.borrow().is_empty(), "guard should clear on drop");
});
}
#[test]
fn finish_deduplicates_and_pairs_revisions() {
let mut store = FileInputStore::new();
store.insert(
FileId::new(1),
crate::input::FileInput::new(Default::default()),
);
store.insert(
FileId::new(2),
crate::input::FileInput::new(Default::default()),
);
let guard = DependencyRecorderGuard::new();
record_file_dep(FileId::new(1));
record_file_dep(FileId::new(2));
record_file_dep(FileId::new(1)); record_file_dep(FileId::INVALID);
let deps = guard.finish(&store);
assert_eq!(deps.len(), 2);
assert_eq!(deps[0], (FileId::new(1), 1));
assert_eq!(deps[1], (FileId::new(2), 1));
}
#[test]
fn guard_clears_on_panic_unwind() {
let result = std::panic::catch_unwind(|| {
let _guard = DependencyRecorderGuard::new();
record_file_dep(FileId::new(99));
panic!("simulated query panic");
});
assert!(result.is_err());
FILE_DEPS.with(|deps| {
assert!(
deps.borrow().is_empty(),
"guard should clear on panic unwind"
);
});
}
}