vibesql_executor/cache/
integration.rs1use std::{collections::HashSet, sync::Arc};
4
5use super::{QueryPlanCache, QueryResultCache, QuerySignature};
6use crate::schema::CombinedSchema;
7use vibesql_storage::Row;
8
9pub struct CacheManager {
11 plan_cache: Arc<QueryPlanCache>,
12 result_cache: Arc<QueryResultCache>,
13}
14
15impl CacheManager {
16 pub fn new(max_size: usize) -> Self {
18 Self::with_separate_limits(max_size, max_size)
19 }
20
21 pub fn with_separate_limits(plan_cache_size: usize, result_cache_size: usize) -> Self {
23 Self {
24 plan_cache: Arc::new(QueryPlanCache::new(plan_cache_size)),
25 result_cache: Arc::new(QueryResultCache::new(result_cache_size)),
26 }
27 }
28
29 pub fn get_or_create<F>(&self, query: &str, creator: F) -> Result<String, String>
32 where
33 F: FnOnce() -> Result<String, String>,
34 {
35 let signature = QuerySignature::from_sql(query);
36
37 if let Some(cached) = self.plan_cache.get(&signature) {
39 return Ok(cached);
40 }
41
42 let plan = creator()?;
44 self.plan_cache.insert(signature, plan.clone());
45 Ok(plan)
46 }
47
48 pub fn get_result(&self, signature: &QuerySignature) -> Option<(Vec<Row>, CombinedSchema)> {
50 self.result_cache.get(signature)
51 }
52
53 pub fn insert_result(
55 &self,
56 signature: QuerySignature,
57 rows: Vec<Row>,
58 schema: CombinedSchema,
59 tables: HashSet<String>,
60 ) {
61 self.result_cache.insert(signature, rows, schema, tables);
62 }
63
64 pub fn invalidate_table(&self, table_name: &str) {
66 self.plan_cache.invalidate_table(table_name);
67 self.result_cache.invalidate_table(table_name);
68 }
69
70 pub fn clear(&self) {
72 self.plan_cache.clear();
73 self.result_cache.clear();
74 }
75
76 pub fn plan_stats(&self) -> super::CacheStats {
78 self.plan_cache.stats()
79 }
80
81 pub fn result_stats(&self) -> super::CacheStats {
83 self.result_cache.stats()
84 }
85
86 pub fn stats(&self) -> super::CacheStats {
88 self.plan_stats()
89 }
90}
91
92pub struct CachedQueryContext {
94 manager: CacheManager,
95}
96
97impl CachedQueryContext {
98 pub fn new(max_cache_size: usize) -> Self {
100 Self { manager: CacheManager::new(max_cache_size) }
101 }
102
103 pub fn execute<F>(&self, query: &str, executor: F) -> Result<Vec<String>, String>
105 where
106 F: FnOnce(&str) -> Result<Vec<String>, String>,
107 {
108 let plan = self.manager.get_or_create(query, || Ok(format!("plan_for_{}", query)))?;
109
110 executor(&plan)
111 }
112
113 pub fn stats(&self) -> super::CacheStats {
115 self.manager.stats()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::{
122 super::{LiteralValue, ParameterizedPlan},
123 *,
124 };
125
126 #[test]
127 fn test_cache_manager_get_or_create() {
128 let manager = CacheManager::new(10);
129
130 let result1 = manager.get_or_create("SELECT * FROM users", || Ok("plan1".to_string()));
131
132 assert!(result1.is_ok());
133 assert_eq!(result1.unwrap(), "plan1");
134
135 let result2 = manager.get_or_create("SELECT * FROM users", || Ok("plan2".to_string()));
137
138 assert!(result2.is_ok());
139 assert_eq!(result2.unwrap(), "plan1"); }
141
142 #[test]
143 fn test_cache_manager_invalidate_table() {
144 let manager = CacheManager::new(10);
145
146 let signature = super::QuerySignature::from_sql("SELECT * FROM users");
152 let mut tables = std::collections::HashSet::new();
153 tables.insert("users".to_string());
154 manager.plan_cache.insert_with_tables(signature, "SELECT * FROM users".to_string(), tables);
155
156 assert_eq!(manager.stats().size, 1);
157
158 manager.invalidate_table("users");
159 let stats = manager.stats();
160 assert_eq!(stats.size, 0);
161 }
162
163 #[test]
164 fn test_cache_manager_no_table_tracking_by_default() {
165 let manager = CacheManager::new(10);
167
168 manager
169 .get_or_create("SELECT * FROM users", || Ok("SELECT * FROM users".to_string()))
170 .unwrap();
171
172 manager.invalidate_table("users");
174 let stats = manager.stats();
175
176 assert_eq!(stats.size, 1);
178 }
179
180 #[test]
181 fn test_cache_manager_clear() {
182 let manager = CacheManager::new(10);
183
184 manager.get_or_create("SELECT * FROM users", || Ok("plan".to_string())).unwrap();
185
186 assert_eq!(manager.stats().size, 1);
187 manager.clear();
188 assert_eq!(manager.stats().size, 0);
189 }
190
191 #[test]
192 fn test_cached_query_context() {
193 let ctx = CachedQueryContext::new(10);
194
195 let result = ctx.execute("SELECT * FROM users", |plan| Ok(vec![plan.to_string()]));
196
197 assert!(result.is_ok());
198 }
199
200 #[test]
201 fn test_repeated_query_patterns() {
202 let manager = CacheManager::new(100);
203 let mut call_count = 0;
204
205 manager
207 .get_or_create("SELECT col0 FROM tab WHERE col1 > 5", || {
208 call_count += 1;
209 Ok("plan1".to_string())
210 })
211 .unwrap();
212
213 manager
215 .get_or_create("SELECT col0 FROM tab WHERE col1 > 5", || {
216 call_count += 1;
217 Ok("plan1".to_string())
218 })
219 .unwrap();
220
221 assert_eq!(call_count, 1);
223 assert_eq!(manager.stats().hits, 1);
224 }
225
226 #[test]
227 fn test_cache_manager_with_error() {
228 let manager = CacheManager::new(10);
229
230 let result =
231 manager.get_or_create("SELECT * FROM users", || Err("creation failed".to_string()));
232
233 assert!(result.is_err());
234 }
235
236 #[test]
237 fn test_parameterized_plan_example() {
238 let plan = ParameterizedPlan::new(
239 "SELECT * FROM users WHERE age > ?".to_string(),
240 vec![super::super::ParameterPosition { position: 40, context: "age".to_string() }],
241 vec![LiteralValue::Integer(25)],
242 );
243
244 let bound1 = plan.bind(&[LiteralValue::Integer(30)]).unwrap();
245 let bound2 = plan.bind(&[LiteralValue::Integer(40)]).unwrap();
246
247 assert_eq!(bound1, "SELECT * FROM users WHERE age > 30");
248 assert_eq!(bound2, "SELECT * FROM users WHERE age > 40");
249 }
250}