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