vibesql_executor/cache/
integration.rs

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