1use crate::prelude::*;
6
7use rusqlite::{Connection, Transaction, ToSql, types::ValueRef};
8
9type RE = rusqlite::Error;
10
11const MAX_RETRIES: u32 = 10;
12
13#[derive(Debug,Copy,Clone)]
14#[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
15#[derive(EnumIter)]
16pub enum RefreshKind {
17 XsaGit,
18 XsaParsed,
19 CveListXenSec,
20 CveListUpstream,
21 UpstreamMr(XsaNum),
22}
23
24type Refreshed = BTreeSet<Refresh>;
25
26#[derive(Debug,Copy,Clone)]
27#[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
28pub struct Refresh(RefreshKind );
29
30impl RefreshKind {
31 pub fn bind(self, xsa: Option<XsaNum>) -> Refresh {
32 use RefreshKind as R;
33 Refresh(match self {
34 R::UpstreamMr(old) => { assert_eq!(old,0); R::UpstreamMr(xsa.unwrap()) },
35 other => other,
36 })
37 }
38}
39
40#[derive(Debug,Copy,Clone)]
41#[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
42pub struct RefreshingToken(Refresh);
43
44impl Config {
45 pub fn want_refresh(&self, rk: RefreshKind, xsa: Option<XsaNum>)
46 -> Option<RefreshingToken>
47 {
48 use RefreshKind as R;
49 let want = match rk {
50 R::XsaGit |
51 R::CveListXenSec |
52 R::CveListUpstream => ! self.co.no_pull,
53 R::XsaParsed |
54 R::UpstreamMr(_) => self.co.refresh,
55 };
56 if want {
57 Some(RefreshingToken(rk.bind(xsa)))
58 } else {
59 None
60 }
61 }
62}
63
64impl Ctx {
65 #[throws(AE)]
66 pub fn want_remote_actions(&self, xsa: XsaNum,
67 desc: &str, info: &dyn Debug)
68 -> bool
69 {
70 use RefreshKind as R;
71 let want = ! self.cfg.co.dry_run;
72 if want {
73 if self.cfg.co.no_pull { throw!(anyhow!(
74 "--no-pull specified with action which makes remote updats"
75 )); }
76
77 for rf in RefreshKind::iter() {
78 let rf = rf.bind(Some(xsa));
79 match rf {
80 Refresh(R::XsaParsed) => {
81 },
83 rf => {
84 assert!(self.cache.refreshed.contains(&rf),
85 "program logic error, did not refresh {:?}, did {:?}",
86 rf, &self.cache.refreshed);
87 }
88 }
89 }
90 } else {
91 eprintln!("dry run, would {} {:?}", desc, info);
92 }
93 want
94 }
95}
96
97impl Cache {
98 pub fn did_refresh(&mut self, rt: Option<RefreshingToken>) {
99 if let Some(rt) = rt {
100 self.refreshed.insert(rt.0);
101 }
102 }
103}
104
105static SCHEMA: &[&str] = &[r#"
106 CREATE TABLE IF NOT EXISTS xsagit_parsed_1 (
107 xsa INTEGER PRIMARY KEY,
108 vsn INTEGER NOT NULL,
109 json TEXT NOT NULL
110 );
111"#,r#"
112 CREATE TABLE IF NOT EXISTS cvelist_mr_1 (
113 xsa INTEGER PRIMARY KEY,
114 json TEXT NOT NULL
115 );
116"#];
117
118pub trait Cacheable: Serialize + DeserializeOwned + Debug {
119 type Key: Debug;
120 const TABLE: &'static str;
121 const KEY_KCOLS: &'static [&'static str];
122 const KEY_VCOLS: &'static [&'static str];
123 const REFRESH: RefreshKind;
124 fn key_col_values(key: &Self::Key) -> Result<Vec<&dyn ToSql>, AE>;
125}
126
127impl Cacheable for xsagit::Parsed {
128 type Key = (XsaNum, XsaVsn);
137 const TABLE: &'static str = "xsagit_parsed_1";
138 const KEY_KCOLS: &'static [&'static str] = &["xsa"];
139 const KEY_VCOLS: &'static [&'static str] = &["vsn"];
140 const REFRESH: RefreshKind = RefreshKind::XsaParsed;
141 #[throws(AE)]
142 fn key_col_values<'k>((xsa, vsn): &'k (XsaNum, XsaVsn)) -> Vec<&'k dyn ToSql>
143 { vec![xsa as _, vsn as _] }
144}
145
146impl Cacheable for upstream::MrState {
147 type Key = XsaNum;
161 const TABLE: &'static str = "cvelist_mr_1";
162 const KEY_KCOLS: &'static [&'static str] = &["xsa"];
163 const KEY_VCOLS: &'static [&'static str] = &[];
164 const REFRESH: RefreshKind = RefreshKind::UpstreamMr(0);
165 #[throws(AE)]
166 fn key_col_values<'k>(xsa: &'k XsaNum) -> Vec<&'k dyn ToSql>
167 { vec![xsa as _] }
168}
169
170#[derive(Debug)]
171pub struct Cache {
172 pub cfg: Arc<Config>,
173 pub refreshed: Refreshed,
174 db: Connection,
175}
176
177#[derive(Debug,Copy,Clone)]
178pub struct XsaDoneData {
179 done: Option<XsaVsn>,
180}
181
182#[throws(fmt::Error)]
183fn write_cond_where_cols<'k,S,C>(mut stmt: S, cols: C)
184where S: fmt::Write,
185 C: Iterator<Item=&'k str>
186{
187 for (k, conj) in izip!(
188 cols,
189 iter::once("WHERE").chain(iter::repeat("AND")),
190 ) {
191 write!(stmt, " {} {}=?", conj, k)?;
192 }
193}
194
195impl Cache {
196 #[throws(AE)]
197 pub fn new(cfg: &Arc<Config>) -> Self {
198 let db = rusqlite::Connection::open(&cfg.done_cache)
199 .with_context(|| cfg.done_cache.clone()).context("opne db")?;
200
201 for stmt in SCHEMA {
202 db.execute(stmt,[])
203 .with_context(|| stmt.to_owned())
204 .context("ensure schema is set up")?;
205 }
206
207 Cache { db, cfg: cfg.clone(), refreshed: default() }
208 }
209
210 #[throws(AE)]
211 pub fn with<F,O>(&mut self, mut f: F) -> O
212 where F: FnMut(&mut Transaction) -> Result<O, AE>
213 {
214 use rusqlite::ffi::ErrorCode as E;
215 'ret: loop { for retry in 0.. { match (||{
216 let mut trans = self.db.transaction().context("start transaction")?;
217 let r = f(&mut trans)?;
218 trans.commit().context("commit")?;
219 Ok::<_,AE>(r)
220 })() {
221 Err(e) => match e.downcast_ref() {
222 Some(RE::SqliteFailure(ffie, _))
223 if matches!(ffie.code, E::DatabaseBusy | E::DatabaseLocked)
224 => {
225 if retry >= MAX_RETRIES {
226 throw!(e.context("too many retries"));
227 }
228 continue;
229 },
230 _ => throw!(e),
231 }
232 Ok(r) => break 'ret r,
233 } } }
234 }
235
236 #[throws(AE)]
237 pub fn invalidate<T>(&self, key: &T::Key)
238 where T: Cacheable,
239 {
240 let keys = T::key_col_values(key).context("get keys as params")?;
241 let keys = &keys[0.. T::KEY_KCOLS.len()];
242 let stmt = (||{
243 let mut stmt = String::new();
244 write!(stmt, "DELETE FROM {}", T::TABLE)?;
245 write_cond_where_cols(&mut stmt, T::KEY_KCOLS.iter().cloned())?;
246 Ok::<_,fmt::Error>(stmt)
247 })().unwrap();
248
249 (||{
250 let mut stmt = self.db.prepare_cached(&stmt).context("prepare")?;
251 stmt.execute(&*keys).context("execute delete")?;
252 Ok::<_,AE>(())
253 })()
254 .with_context(|| format!("key={:?}", key))
255 .context("invalidate cache line")?;
256 }
257
258 #[throws(AE)]
259 pub fn lookup<T,F>(&mut self, xsa: XsaNum, key: &T::Key, mut calc: F) -> T
260 where T: Cacheable,
261 F: FnMut(&T::Key) -> Result<T,AE>
262 {
263 let all_cols = || itertools::chain(T::KEY_KCOLS, T::KEY_VCOLS).cloned();
264
265 let stmt = (||{
266 let mut stmt = String::new();
267 write!(stmt, "SELECT json FROM {}", T::TABLE)?;
268 write_cond_where_cols(&mut stmt, &mut all_cols())?;
269 Ok::<_,fmt::Error>(stmt)
270 })().unwrap();
271
272 let got = (||{
273 let keys = T::key_col_values(key).context("get keys as params")?;
274 assert_eq!( keys.len(), all_cols().count(), "{:?}", key );
275
276 let (data, refresh_token) = (||{
277 let refresh_token = self.cfg.want_refresh(T::REFRESH, Some(xsa));
278 if refresh_token.is_some() { return Ok((None, refresh_token)); }
279
280 let mut stmt = self.db.prepare_cached(&stmt).context("prepare")?;
281 let mut data = stmt.query(keys.as_slice()).context("query")?;
282 let data = data.next().context("get query row")?;
283 let data = data.map(|row| {
284 let data = row.get_ref(0).context("get result json column")?;
285 let data = match data {
286 ValueRef::Text(s) => s,
287 x => throw!(anyhow!("got wrong type, expected Text {:?}", &x)),
288 };
289 let data = str::from_utf8(data).context("got non-UTF-8 json")?;
290 let data = serde_json::from_str(data).context("parse JSON")?;
291 Ok::<T,AE>(data)
292 })
293 .transpose()?;
294 Ok::<(Option<T>, Option<RefreshingToken>),AE>((data, refresh_token))
295 })()
296 .with_context(|| stmt.clone())
297 .context("look up in cache")?;
298
299 let hit = data.is_some();
300 let data = data.map(Ok).unwrap_or_else(|| {
301 let data = calc(key).context("calculate, after cache miss")?;
302
303 let stmt = (||{
304 let mut stmt = String::new();
305 write!(stmt, "INSERT OR REPLACE INTO {} (", T::TABLE)?;
306 for k in all_cols() { write!(stmt, "{},", k)?; }
307 write!(stmt, "json) VALUES (")?;
308 for _ in all_cols() { write!(stmt, "?,")?; }
309 write!(stmt, "?)")?;
310 Ok::<_,fmt::Error>(stmt)
311 })().unwrap();
312
313 (||{
314 let json = serde_json::to_string(&data).context("make JSON")?;
315 let mut stmt = self.db.prepare_cached(&stmt).context("prepare")?;
316 let mut keys = keys;
317 keys.push(&json);
318 stmt.execute(&*keys).context("execute insert")?;
319 Ok::<_,AE>(())
320 })()
321 .with_context(|| stmt.clone())
322 .context("save in cache")?;
323
324 self.did_refresh(refresh_token);
325
326 Ok::<T,AE>(data)
327 })?;
328 Ok::<_,AE>((data, hit))
329 })();
330
331 trace!("lookup {} key={:?} {} got={:?}",
332 T::TABLE, key,
333 match got {
334 Err(_) => "error",
335 Ok((_,false)) => "miss",
336 Ok((_,true )) => "hit",
337 },
338 &got);
339
340 got
341 .with_context(|| format!("key={:?}", key))
342 .context("lookup in cache db")?
343 .0
344 }
345}