use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::sync::Arc;
use crate::metadata::StructMetadata;
use crate::schema_macro::circular::{CircularAnalysis, analyze_circular_refs};
use crate::schema_macro::file_lookup::{
find_fk_column_from_target_entity, find_struct_from_schema_path,
};
use super::{FILE_CACHE, FileCache, PathLookupEntry, ensure_file_list, get_fingerprint_cached};
enum Probe<T> {
Hit(T),
Miss(u64),
}
pub fn get_circular_analysis(source_module_path: &[String], definition: &str) -> CircularAnalysis {
let key = (source_module_path.join("::"), definition.to_string());
let cached = FILE_CACHE.with(|cache| cache.borrow().circular_analysis.get(&key).cloned());
if let Some(result) = cached {
FILE_CACHE.with(|cache| cache.borrow_mut().circular_cache_hits += 1);
return result;
}
let result = analyze_circular_refs(source_module_path, definition);
FILE_CACHE.with(|cache| {
cache
.borrow_mut()
.circular_analysis
.insert(key, result.clone());
});
result
}
fn path_lookup_fingerprint(cache: &mut FileCache, path_str: &str) -> u64 {
let mut hasher = DefaultHasher::new();
path_str.hash(&mut hasher);
let Some(manifest_dir) = super::get_manifest_dir_inner(cache) else {
return hasher.finish();
};
let src_dir = Path::new(&manifest_dir).join("src");
src_dir.hash(&mut hasher);
let segments: Vec<&str> = path_str
.split("::")
.map(str::trim)
.filter(|s| !s.is_empty())
.filter(|s| *s != "crate" && *s != "self" && *s != "super")
.collect();
if segments.len() <= 1 {
let files = ensure_file_list(cache, &src_dir);
for path in files.iter() {
fingerprint_path(cache, path, &mut hasher);
}
return hasher.finish();
}
let module_segments = &segments[..segments.len() - 1];
let joined = module_segments.join("/");
let candidates = [
src_dir.join(format!("{joined}.rs")),
src_dir.join(format!("{joined}/mod.rs")),
];
for path in &candidates {
fingerprint_path(cache, path, &mut hasher);
}
hasher.finish()
}
fn fingerprint_path(cache: &mut FileCache, path: &Path, hasher: &mut DefaultHasher) {
path.hash(hasher);
match get_fingerprint_cached(cache, path) {
Some(fp) => {
"fp:some".hash(hasher);
if let Ok(duration) = fp.mtime.duration_since(std::time::UNIX_EPOCH) {
duration.as_secs().hash(hasher);
duration.subsec_nanos().hash(hasher);
}
fp.len.hash(hasher);
}
None => "fp:none".hash(hasher),
}
}
pub fn get_struct_from_schema_path(path_str: &str) -> Option<Arc<StructMetadata>> {
let probe = FILE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let epoch = cache.epoch;
if let Some(entry) = cache.struct_lookup.get(path_str)
&& entry.last_epoch_validated == epoch
{
return Probe::Hit(entry.value.clone());
}
let fingerprint = path_lookup_fingerprint(&mut cache, path_str);
if let Some(entry) = cache.struct_lookup.get_mut(path_str)
&& entry.fingerprint == fingerprint
{
entry.last_epoch_validated = epoch;
return Probe::Hit(entry.value.clone());
}
Probe::Miss(fingerprint)
});
let fingerprint = match probe {
Probe::Hit(value) => {
FILE_CACHE.with(|cache| cache.borrow_mut().struct_lookup_cache_hits += 1);
return value;
}
Probe::Miss(fingerprint) => fingerprint,
};
let result = find_struct_from_schema_path(path_str).map(Arc::new);
FILE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let epoch = cache.epoch;
cache.struct_lookup.insert(
path_str.to_string(),
PathLookupEntry {
value: result.clone(),
fingerprint,
last_epoch_validated: epoch,
},
);
});
result
}
pub fn get_fk_column(schema_path: &str, via_rel: &str) -> Option<String> {
let key = (schema_path.to_string(), via_rel.to_string());
let probe = FILE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let epoch = cache.epoch;
if let Some(entry) = cache.fk_column_lookup.get(&key)
&& entry.last_epoch_validated == epoch
{
return Probe::Hit(entry.value.clone());
}
let fingerprint = path_lookup_fingerprint(&mut cache, schema_path);
if let Some(entry) = cache.fk_column_lookup.get_mut(&key)
&& entry.fingerprint == fingerprint
{
entry.last_epoch_validated = epoch;
return Probe::Hit(entry.value.clone());
}
Probe::Miss(fingerprint)
});
let fingerprint = match probe {
Probe::Hit(value) => {
FILE_CACHE.with(|cache| cache.borrow_mut().fk_column_cache_hits += 1);
return value;
}
Probe::Miss(fingerprint) => fingerprint,
};
let result = find_fk_column_from_target_entity(schema_path, via_rel);
FILE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let epoch = cache.epoch;
cache.fk_column_lookup.insert(
key,
PathLookupEntry {
value: result.clone(),
fingerprint,
last_epoch_validated: epoch,
},
);
});
result
}
pub fn get_module_path_from_schema_path(schema_path: &proc_macro2::TokenStream) -> Vec<String> {
let path_str = schema_path.to_string();
let cached = FILE_CACHE.with(|cache| cache.borrow().module_path_cache.get(&path_str).cloned());
if let Some(result) = cached {
FILE_CACHE.with(|cache| cache.borrow_mut().module_path_cache_hits += 1);
return result;
}
let mut result: Vec<String> = path_str
.split("::")
.map(str::trim)
.filter(|s| !s.is_empty())
.map(ToString::to_string)
.collect();
result.pop();
FILE_CACHE.with(|cache| {
cache
.borrow_mut()
.module_path_cache
.insert(path_str, result.clone());
});
result
}
#[cfg(test)]
mod tests;