Skip to main content

sqry_core/query/cache/
mod.rs

1//! Query caching module
2
3mod ast_parse_cache;
4mod budget;
5mod result_cache;
6mod types;
7
8pub use ast_parse_cache::AstParseCache;
9pub use budget::{BudgetConfig, BudgetStats, CacheBudgetController, ClampAction, ClampReason};
10pub use result_cache::ResultCache;
11pub use types::{CacheKey, CacheStats};
12
13use std::collections::HashMap;
14use std::collections::hash_map::DefaultHasher;
15use std::hash::{BuildHasher, Hasher};
16use std::path::{Path, PathBuf};
17use std::time::SystemTime;
18
19/// File metadata for cache invalidation (local definition after legacy index removal)
20#[derive(Debug, Clone)]
21pub struct FileMetadata {
22    /// Path to the file
23    pub path: PathBuf,
24    /// Last modification time
25    pub modified_time: SystemTime,
26    /// File size in bytes
27    pub size: u64,
28}
29
30/// Compute hash of file metadata set
31///
32/// This hashes (path, mtime, size) for all files in deterministic order.
33/// Uses `to_string_lossy` for cross-platform path compatibility.
34#[must_use]
35pub fn compute_file_set_hash<S>(file_metadata: &HashMap<PathBuf, FileMetadata, S>) -> u64
36where
37    S: BuildHasher,
38{
39    let mut hasher = DefaultHasher::new();
40
41    // Sort paths for deterministic ordering
42    let mut paths: Vec<_> = file_metadata.keys().collect();
43    paths.sort();
44
45    for path in paths {
46        let meta = &file_metadata[path];
47
48        // Hash (path, mtime, size) tuple
49        // Use to_string_lossy for cross-platform compatibility (Windows safe)
50        hasher.write(path.to_string_lossy().as_bytes());
51
52        // Hash mtime (as milliseconds for better precision)
53        if let Ok(duration) = meta.modified_time.duration_since(std::time::UNIX_EPOCH) {
54            hasher.write(&duration.as_millis().to_le_bytes());
55        }
56
57        hasher.write(&meta.size.to_le_bytes());
58    }
59
60    hasher.finish()
61}
62
63/// Compute hash of workspace root path
64///
65/// Uses `to_string_lossy` for cross-platform compatibility.
66#[must_use]
67pub fn compute_root_path_hash(root: &Path) -> u64 {
68    let mut hasher = DefaultHasher::new();
69    // Use to_string_lossy for cross-platform compatibility (Windows safe)
70    hasher.write(root.to_string_lossy().as_bytes());
71    hasher.finish()
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use std::time::SystemTime;
78
79    #[test]
80    fn file_set_hash_deterministic() {
81        let mut metadata = HashMap::new();
82        metadata.insert(
83            PathBuf::from("file1.rs"),
84            FileMetadata {
85                path: PathBuf::from("file1.rs"),
86                modified_time: SystemTime::now(),
87                size: 100,
88            },
89        );
90
91        let hash1 = compute_file_set_hash(&metadata);
92        let hash2 = compute_file_set_hash(&metadata);
93
94        assert_eq!(hash1, hash2, "Hash should be deterministic");
95    }
96
97    #[test]
98    fn root_path_hash_deterministic() {
99        let path = Path::new("/home/user/project");
100        let hash1 = compute_root_path_hash(path);
101        let hash2 = compute_root_path_hash(path);
102
103        assert_eq!(hash1, hash2, "Hash should be deterministic");
104    }
105
106    #[test]
107    fn file_set_hash_changes_on_modification() {
108        let time1 = SystemTime::now();
109        let mut metadata1 = HashMap::new();
110        metadata1.insert(
111            PathBuf::from("file1.rs"),
112            FileMetadata {
113                path: PathBuf::from("file1.rs"),
114                modified_time: time1,
115                size: 100,
116            },
117        );
118
119        let mut metadata2 = HashMap::new();
120        metadata2.insert(
121            PathBuf::from("file1.rs"),
122            FileMetadata {
123                path: PathBuf::from("file1.rs"),
124                modified_time: time1,
125                size: 200, // Changed
126            },
127        );
128
129        let hash1 = compute_file_set_hash(&metadata1);
130        let hash2 = compute_file_set_hash(&metadata2);
131
132        assert_ne!(hash1, hash2, "Hash should change when file changes");
133    }
134}