vibesql_storage/database/
cache.rs1use std::sync::Arc;
6
7use super::core::Database;
8use crate::{columnar_cache::ColumnarCache, StorageError};
9
10impl Database {
11 pub fn get_columnar(
36 &self,
37 table_name: &str,
38 ) -> Result<Option<Arc<crate::ColumnarTable>>, StorageError> {
39 if let Some(cached) = self.columnar_cache.get(table_name) {
41 return Ok(Some(cached));
42 }
43
44 let table = match self.get_table(table_name) {
46 Some(t) => t,
47 None => return Ok(None),
48 };
49
50 let columnar = table.scan_columnar()?;
52
53 let cached = self.columnar_cache.insert(table_name, columnar);
55 Ok(Some(cached))
56 }
57
58 pub fn invalidate_columnar_cache(&self, table_name: &str) {
63 self.columnar_cache.invalidate(table_name);
64 }
65
66 pub fn clear_columnar_cache(&self) {
68 self.columnar_cache.clear();
69 }
70
71 pub fn columnar_cache_stats(&self) -> crate::columnar_cache::CacheStats {
76 self.columnar_cache.stats()
77 }
78
79 pub fn columnar_cache_memory_usage(&self) -> usize {
81 self.columnar_cache.memory_usage()
82 }
83
84 pub fn columnar_cache_budget(&self) -> usize {
86 self.columnar_cache.max_memory()
87 }
88
89 pub fn set_columnar_cache_budget(&mut self, max_bytes: usize) {
94 self.columnar_cache = Arc::new(ColumnarCache::new(max_bytes));
95 }
96
97 pub fn pre_warm_columnar_cache(&self, table_names: &[&str]) -> Result<usize, StorageError> {
123 let mut count = 0;
124 for table_name in table_names {
125 if self.get_columnar(table_name)?.is_some() {
127 count += 1;
128 }
129 }
130 Ok(count)
131 }
132
133 pub fn pre_warm_all_columnar(&self) -> Result<usize, StorageError> {
149 let table_names: Vec<String> = self.list_tables();
150 let refs: Vec<&str> = table_names.iter().map(|s| s.as_str()).collect();
151 self.pre_warm_columnar_cache(&refs)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use vibesql_catalog::{ColumnSchema, TableSchema};
158 use vibesql_types::{DataType, SqlValue};
159
160 use super::*;
161 use crate::Row;
162
163 fn create_test_table_schema(name: &str) -> TableSchema {
164 TableSchema::new(
165 name.to_string(),
166 vec![
167 ColumnSchema::new("id".to_string(), DataType::Integer, false),
168 ColumnSchema::new(
169 "name".to_string(),
170 DataType::Varchar { max_length: Some(255) },
171 true,
172 ),
173 ],
174 )
175 }
176
177 fn create_test_rows(count: usize) -> Vec<Row> {
178 (0..count)
179 .map(|i| {
180 Row::new(vec![
181 SqlValue::Integer(i as i64),
182 SqlValue::Varchar(arcstr::ArcStr::from(format!("name_{}", i))),
183 ])
184 })
185 .collect()
186 }
187
188 #[test]
189 fn test_pre_warm_columnar_cache_with_valid_tables() {
190 let mut db = Database::new();
191
192 db.create_table(create_test_table_schema("table1")).unwrap();
194 db.create_table(create_test_table_schema("table2")).unwrap();
195
196 for row in create_test_rows(10) {
198 db.insert_row("table1", row).unwrap();
199 }
200 for row in create_test_rows(5) {
201 db.insert_row("table2", row).unwrap();
202 }
203
204 let count = db.pre_warm_columnar_cache(&["table1", "table2"]).unwrap();
206 assert_eq!(count, 2, "Should have pre-warmed 2 tables");
207
208 let stats = db.columnar_cache_stats();
210 assert_eq!(stats.conversions, 2, "Should have converted 2 tables");
211 }
212
213 #[test]
214 fn test_pre_warm_columnar_cache_nonexistent_table() {
215 let db = Database::new();
216
217 let count = db.pre_warm_columnar_cache(&["nonexistent1", "nonexistent2"]).unwrap();
219 assert_eq!(count, 0, "Should return 0 for nonexistent tables");
220
221 let stats = db.columnar_cache_stats();
223 assert_eq!(stats.conversions, 0, "Should have 0 conversions for nonexistent tables");
224 }
225
226 #[test]
227 fn test_pre_warm_columnar_cache_mixed_tables() {
228 let mut db = Database::new();
229
230 db.create_table(create_test_table_schema("exists")).unwrap();
232 for row in create_test_rows(5) {
233 db.insert_row("exists", row).unwrap();
234 }
235
236 let count = db.pre_warm_columnar_cache(&["exists", "nonexistent"]).unwrap();
238 assert_eq!(count, 1, "Should have pre-warmed only 1 existing table");
239 }
240
241 #[test]
242 fn test_pre_warm_all_columnar() {
243 let mut db = Database::new();
244
245 db.create_table(create_test_table_schema("table_a")).unwrap();
247 db.create_table(create_test_table_schema("table_b")).unwrap();
248 db.create_table(create_test_table_schema("table_c")).unwrap();
249
250 for row in create_test_rows(5) {
252 db.insert_row("table_a", row).unwrap();
253 }
254 for row in create_test_rows(3) {
255 db.insert_row("table_b", row).unwrap();
256 }
257 for row in create_test_rows(7) {
258 db.insert_row("table_c", row).unwrap();
259 }
260
261 let count = db.pre_warm_all_columnar().unwrap();
263 assert_eq!(count, 3, "Should have pre-warmed all 3 tables");
264
265 let stats = db.columnar_cache_stats();
267 assert_eq!(stats.conversions, 3, "Should have converted all 3 tables");
268 }
269
270 #[test]
271 fn test_pre_warm_results_in_cache_hits() {
272 let mut db = Database::new();
273
274 db.create_table(create_test_table_schema("cached_table")).unwrap();
276 for row in create_test_rows(10) {
277 db.insert_row("cached_table", row).unwrap();
278 }
279
280 let count = db.pre_warm_columnar_cache(&["cached_table"]).unwrap();
282 assert_eq!(count, 1);
283
284 let stats_before = db.columnar_cache_stats();
286 let hits_before = stats_before.hits;
287
288 let _ = db.get_columnar("cached_table").unwrap();
290
291 let stats_after = db.columnar_cache_stats();
293 assert_eq!(
294 stats_after.hits,
295 hits_before + 1,
296 "Should have one more cache hit after accessing pre-warmed table"
297 );
298 assert_eq!(
299 stats_after.conversions, stats_before.conversions,
300 "Should not have additional conversions"
301 );
302 }
303
304 #[test]
305 fn test_pre_warm_empty_table_list() {
306 let db = Database::new();
307
308 let count = db.pre_warm_columnar_cache(&[]).unwrap();
310 assert_eq!(count, 0, "Should return 0 for empty table list");
311 }
312
313 #[test]
314 fn test_pre_warm_all_empty_database() {
315 let db = Database::new();
316
317 let count = db.pre_warm_all_columnar().unwrap();
319 assert_eq!(count, 0, "Should return 0 for empty database");
320 }
321
322 #[test]
323 fn test_pre_warm_idempotent() {
324 let mut db = Database::new();
325
326 db.create_table(create_test_table_schema("test_table")).unwrap();
328 for row in create_test_rows(5) {
329 db.insert_row("test_table", row).unwrap();
330 }
331
332 let count1 = db.pre_warm_columnar_cache(&["test_table"]).unwrap();
334 let stats1 = db.columnar_cache_stats();
335
336 let count2 = db.pre_warm_columnar_cache(&["test_table"]).unwrap();
337 let stats2 = db.columnar_cache_stats();
338
339 assert_eq!(count1, 1);
341 assert_eq!(count2, 1);
342
343 assert_eq!(stats1.conversions, 1);
345 assert_eq!(stats2.conversions, 1, "Second pre-warm should not cause additional conversion");
346 assert_eq!(stats2.hits, stats1.hits + 1, "Second pre-warm should result in cache hit");
347 }
348}