vibesql_executor/cache/
integration.rs

1//! Cache management and integration utilities
2
3use std::{collections::HashSet, sync::Arc};
4
5use super::{QueryPlanCache, QueryResultCache, QuerySignature};
6use crate::schema::CombinedSchema;
7use vibesql_storage::Row;
8
9/// Simplified cache manager for common operations
10pub struct CacheManager {
11    plan_cache: Arc<QueryPlanCache>,
12    result_cache: Arc<QueryResultCache>,
13}
14
15impl CacheManager {
16    /// Create a new cache manager with separate plan and result caches
17    pub fn new(max_size: usize) -> Self {
18        Self::with_separate_limits(max_size, max_size)
19    }
20
21    /// Create cache manager with different limits for plans and results
22    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    /// Get or create cached query plan
30    /// Note: The creator function should return the parsed query, not execute it
31    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        // Try cache hit
38        if let Some(cached) = self.plan_cache.get(&signature) {
39            return Ok(cached);
40        }
41
42        // Cache miss - create new plan
43        let plan = creator()?;
44        self.plan_cache.insert(signature, plan.clone());
45        Ok(plan)
46    }
47
48    /// Get cached query result
49    pub fn get_result(&self, signature: &QuerySignature) -> Option<(Vec<Row>, CombinedSchema)> {
50        self.result_cache.get(signature)
51    }
52
53    /// Insert query result into cache
54    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    /// Invalidate all plans and results referencing a table
65    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    /// Clear all cached plans and results
71    pub fn clear(&self) {
72        self.plan_cache.clear();
73        self.result_cache.clear();
74    }
75
76    /// Get plan cache statistics
77    pub fn plan_stats(&self) -> super::CacheStats {
78        self.plan_cache.stats()
79    }
80
81    /// Get result cache statistics
82    pub fn result_stats(&self) -> super::CacheStats {
83        self.result_cache.stats()
84    }
85
86    /// Get combined cache statistics (for backwards compatibility)
87    pub fn stats(&self) -> super::CacheStats {
88        self.plan_stats()
89    }
90}
91
92/// Query execution context with caching
93pub struct CachedQueryContext {
94    manager: CacheManager,
95}
96
97impl CachedQueryContext {
98    /// Create a new cached query context
99    pub fn new(max_cache_size: usize) -> Self {
100        Self { manager: CacheManager::new(max_cache_size) }
101    }
102
103    /// Execute a query using cache
104    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    /// Get cache statistics
114    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        // Second call should hit cache
136        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"); // Same as first
140    }
141
142    #[test]
143    fn test_cache_manager_invalidate_table() {
144        let manager = CacheManager::new(10);
145
146        // Note: get_or_create doesn't track table dependencies automatically
147        // Table invalidation only works when using insert_with_tables directly
148        // or when the cache implementation extracts table names from queries
149
150        // Insert with explicit table tracking
151        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        // Document that get_or_create doesn't automatically track table dependencies
166        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        // Invalidation won't work because tables aren't tracked
173        manager.invalidate_table("users");
174        let stats = manager.stats();
175
176        // Entry still exists (size = 1) because no tables were associated
177        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        // First query
206        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        // Same query - should hit cache
214        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        // Should hit cache (exact match)
222        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}