1use std::{collections::BTreeSet, path::Path};
4
5use rusqlite::params;
6use serde::{Deserialize, Serialize, de::DeserializeOwned};
7
8use super::receipts::Error;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct Unreadable {
13 pub kind: String,
14 pub id: String,
15 pub reason: String,
16}
17
18impl std::fmt::Display for Unreadable {
19 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(out, "Unreadable {} {}: {}", self.kind, self.id, self.reason)
21 }
22}
23
24pub(super) fn decode<T: DeserializeOwned>(
25 kind: &'static str,
26 id: &str,
27 data: &str,
28) -> Result<T, Unreadable> {
29 serde_json::from_str(data).map_err(|error| Unreadable {
30 kind: kind.into(),
31 id: id.to_owned(),
32 reason: error.to_string(),
33 })
34}
35
36pub(super) fn evidence<T: DeserializeOwned>(kind: &'static str, data: &str) -> Option<T> {
38 match serde_json::from_str(data) {
39 Ok(record) => Some(record),
40 Err(error) => {
41 #[derive(Deserialize)]
43 struct Identity {
44 id: String,
45 }
46 let id = serde_json::from_str::<Identity>(data)
47 .map(|identity| identity.id)
48 .unwrap_or_else(|_| "(unknown id)".into());
49 skipped(&Unreadable {
50 kind: kind.into(),
51 id,
52 reason: error.to_string(),
53 });
54 None
55 }
56 }
57}
58
59pub(super) fn skipped(unreadable: &Unreadable) {
61 use std::io::Write;
62 let _ = writeln!(std::io::stderr().lock(), "{unreadable}");
63}
64
65pub async fn read_unreadable(state: &Path, repo: Option<&Path>) -> Result<Vec<Unreadable>, String> {
67 super::receipts::check_files(state)?;
68 if !state
69 .join(super::DATABASE)
70 .try_exists()
71 .map_err(|error| error.to_string())?
72 {
73 return Ok(Vec::new());
74 }
75 let connection = tokio_rusqlite::Connection::open_with_flags(
76 state.join(super::DATABASE),
77 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
78 )
79 .await
80 .map_err(|error| error.to_string())?;
81 let repo = repo.map(crate::platform::path_text);
82 let result = connection
83 .call(move |db| -> Result<_, Error> {
84 super::wait_when_busy(db)?;
85 let transaction = db.transaction()?;
86 if !super::receipts::schema_initialized(&transaction)? {
87 return Ok(Vec::new());
88 }
89 let mut statement =
90 transaction.prepare("SELECT id FROM runs WHERE ?1 IS NULL OR repo=?1")?;
91 let runs = statement
92 .query_map(params![repo], |row| row.get::<_, crate::types::RunId>(0))?
93 .collect::<Result<Vec<_>, _>>()?;
94 let mut unreadable = Vec::new();
95 let mut seen = BTreeSet::new();
96 for error in in_runs(&transaction, &runs)?.into_values().flatten() {
97 if seen.insert((error.kind.clone(), error.id.clone())) {
98 unreadable.push(error);
99 }
100 }
101 Ok(unreadable)
102 })
103 .await
104 .map_err(|error| error.to_string());
105 super::connection::finish(connection, result).await
106}
107
108pub(super) fn in_runs(
112 db: &rusqlite::Connection,
113 runs: &[crate::types::RunId],
114) -> Result<std::collections::BTreeMap<crate::types::RunId, Vec<Unreadable>>, Error> {
115 let mut statement = db.prepare_cached(
116 "WITH page(id) AS (SELECT value FROM json_each(?1))
117 SELECT r.id,'run',r.id,r.data FROM runs r JOIN page p ON p.id=r.id
118 UNION ALL SELECT d.run_id,'definitions',d.run_id,d.data FROM run_definitions d JOIN page p ON p.id=d.run_id
119 UNION ALL SELECT q.run_id,'request',q.id,q.data FROM requests q JOIN page p ON p.id=q.run_id
120 UNION ALL SELECT q.run_id,'execution',e.id,e.data FROM (SELECT DISTINCT run_id,execution_id FROM requests WHERE run_id IN (SELECT id FROM page) AND execution_id IS NOT NULL) q JOIN executions e ON e.id=q.execution_id
121 UNION ALL SELECT json_extract(e.data,'$.provenance.runId'),'execution',e.id,e.data FROM executions e WHERE json_extract(e.data,'$.provenance.runId') IN (SELECT id FROM page)",
122 )?;
123 let records = statement.query_map([serde_json::to_string(runs)?], |row| {
124 Ok((
125 row.get::<_, crate::types::RunId>(0)?,
126 row.get::<_, String>(1)?,
127 row.get::<_, String>(2)?,
128 row.get::<_, String>(3)?,
129 ))
130 })?;
131 let mut unreadable: std::collections::BTreeMap<_, Vec<_>> = std::collections::BTreeMap::new();
132 let mut seen = BTreeSet::new();
133 for record in records {
134 let (run, kind, id, data) = record?;
135 if !seen.insert((run.clone(), kind.clone(), id.clone())) {
136 continue;
137 }
138 let error = match kind.as_str() {
139 "run" => decode::<super::Run>("run", &id, &data).err(),
140 "definitions" => decode::<super::definitions::Definitions>("run", &id, &data).err(),
142 "request" => decode::<super::Request>("request", &id, &data).err(),
143 _ => decode::<super::Execution>("execution", &id, &data).err(),
144 };
145 if let Some(error) = error {
146 unreadable.entry(run).or_default().push(error);
147 }
148 }
149 for records in unreadable.values_mut() {
150 records.sort_by(|a, b| (&a.kind, &a.id).cmp(&(&b.kind, &b.id)));
151 }
152 Ok(unreadable)
153}