1use crate::catalog::{Catalog, db_err};
9use orbok_core::{CleanupAction, CleanupPlan, OrbokError, OrbokResult, now_iso8601};
10use rusqlite::params;
11
12#[derive(Debug, Clone, Default)]
14pub struct CleanupOutcome {
15 pub deleted_rows: u64,
16}
17
18pub struct CleanupExecutor<'a> {
22 catalog: &'a Catalog,
23}
24
25impl<'a> CleanupExecutor<'a> {
26 pub fn new(catalog: &'a Catalog) -> Self {
27 Self { catalog }
28 }
29
30 pub fn run_safe(&self, plan: &CleanupPlan) -> OrbokResult<CleanupOutcome> {
34 plan.assert_safe_for_ordinary_cleanup()?;
35 match plan.action {
36 CleanupAction::ClearExpiredSearchCache => self.clear_expired_search_cache(),
37 CleanupAction::ClearSnippetCache => self.clear_snippet_cache(),
38 CleanupAction::ClearTemporaryExtraction => Ok(CleanupOutcome::default()),
39 CleanupAction::RemoveReplacedStaleIndexes => self.remove_replaced_stale_indexes(),
40 _ => Err(OrbokError::CleanupWouldTouchPersistentData),
41 }
42 }
43
44 pub fn run_reset_catalog(
49 &self,
50 plan: &CleanupPlan,
51 keep_settings: bool,
52 ) -> OrbokResult<CleanupOutcome> {
53 if plan.action != CleanupAction::ResetCatalog {
54 return Err(OrbokError::Database(
55 "reset requires a ResetCatalog plan".into(),
56 ));
57 }
58 let mut conn = self.catalog.lock();
59 let tx = conn.transaction().map_err(db_err)?;
60 let mut deleted = 0u64;
61 for table in [
64 "sources",
65 "index_jobs",
66 "search_queries",
67 "snippet_cache",
68 "app_events",
69 "storage_accounting",
70 "cache_engines",
71 "models",
72 ] {
73 deleted += tx
74 .execute(&format!("DELETE FROM {table}"), [])
75 .map_err(db_err)? as u64;
76 }
77 if !keep_settings {
78 deleted += tx.execute("DELETE FROM app_settings", []).map_err(db_err)? as u64;
79 }
80 tx.execute("INSERT INTO chunk_fts(chunk_fts) VALUES('delete-all')", [])
82 .map_err(db_err)?;
83 tx.commit().map_err(db_err)?;
84 Ok(CleanupOutcome {
85 deleted_rows: deleted,
86 })
87 }
88
89 fn clear_expired_search_cache(&self) -> OrbokResult<CleanupOutcome> {
90 let now = now_iso8601();
91 let conn = self.catalog.lock();
92 let mut deleted = conn
93 .execute(
94 "DELETE FROM search_result_cache WHERE expires_at IS NOT NULL AND expires_at < ?1",
95 params![now],
96 )
97 .map_err(db_err)? as u64;
98 deleted += conn
99 .execute(
100 "DELETE FROM search_queries WHERE expires_at IS NOT NULL AND expires_at < ?1",
101 params![now],
102 )
103 .map_err(db_err)? as u64;
104 Ok(CleanupOutcome {
105 deleted_rows: deleted,
106 })
107 }
108
109 fn clear_snippet_cache(&self) -> OrbokResult<CleanupOutcome> {
110 let conn = self.catalog.lock();
111 let deleted = conn
112 .execute("DELETE FROM snippet_cache", [])
113 .map_err(db_err)? as u64;
114 Ok(CleanupOutcome {
115 deleted_rows: deleted,
116 })
117 }
118
119 fn remove_replaced_stale_indexes(&self) -> OrbokResult<CleanupOutcome> {
124 let conn = self.catalog.lock();
125 let deleted = conn
126 .execute(
127 "DELETE FROM chunks WHERE chunk_status IN ('stale','deleted') AND file_id IN \
128 (SELECT file_id FROM chunks WHERE chunk_status = 'active')",
129 [],
130 )
131 .map_err(db_err)? as u64;
132 Ok(CleanupOutcome {
133 deleted_rows: deleted,
134 })
135 }
136}