use std::{collections::BTreeMap, path::Path};
use rusqlite::OptionalExtension;
use tokio_rusqlite::Connection;
use super::{
DATABASE, Execution, Request, Run, RunView, Unreadable,
receipts::{Error, check_files, schema_initialized},
unreadable::{decode, skipped},
};
use crate::{
types::{RequestId, RequestStatus, RunId},
workspace::{canonical_target, outside_workspace},
};
async fn snapshot<T: Send + 'static>(
state: &Path,
read: impl FnOnce(&rusqlite::Connection, &Path) -> Result<T, Error> + Send + 'static,
) -> Result<T, String> {
let state = canonical_target(state).map_err(|e| e.to_string())?;
check_files(&state)?;
let connection = Connection::open_with_flags(
state.join(DATABASE),
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
)
.await
.map_err(|e| e.to_string())?;
let result = connection
.call(move |db| -> Result<T, Error> {
super::wait_when_busy(db)?;
let transaction = db.transaction()?;
if !schema_initialized(&transaction)? {
return Err(Error::Invalid("Run not found.".into()));
}
let value = read(&transaction, &state)?;
transaction.commit()?;
Ok(value)
})
.await
.map_err(|e| e.to_string());
super::connection::finish(connection, result).await
}
fn saved_run(db: &rusqlite::Connection, state: &Path, id: &RunId) -> Result<Run, Error> {
let saved: Option<(String, String)> = db
.query_row("SELECT repo,data FROM runs WHERE id=?", [id], |row| {
Ok((row.get(0)?, row.get(1)?))
})
.optional()?;
let (repo, data) = saved.ok_or_else(|| Error::Invalid("Run not found.".into()))?;
outside_workspace(Path::new(&repo), state).map_err(|e| Error::Invalid(e.to_string()))?;
let mut run: Run =
decode("run", id.as_str(), &data).map_err(|error| Error::Invalid(error.to_string()))?;
run.definitions = super::run_record::definitions(db, id)?
.map_err(|error| Error::Invalid(error.to_string()))?;
Ok(run)
}
fn saved_requests(
db: &rusqlite::Connection,
id: &RunId,
) -> Result<(Vec<Request>, Vec<Unreadable>), Error> {
let mut statement =
db.prepare("SELECT id,data FROM requests WHERE run_id=? ORDER BY ordinal")?;
let rows = statement.query_map([id], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
let (mut requests, mut unreadable) = (Vec::new(), Vec::new());
for row in rows {
let (request, data) = row?;
match decode::<Request>("request", &request, &data) {
Ok(request) => requests.push(request),
Err(record) => {
skipped(&record);
unreadable.push(record);
}
}
}
Ok((requests, unreadable))
}
fn unreadable_executions(db: &rusqlite::Connection, id: &RunId) -> Result<Vec<Unreadable>, Error> {
let mut statement = db.prepare(
"SELECT e.id,e.data FROM executions e WHERE e.id IN (SELECT execution_id FROM requests WHERE run_id=? AND execution_id IS NOT NULL)",
)?;
let rows = statement.query_map([id], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
let mut unreadable = Vec::new();
for row in rows {
let (execution, data) = row?;
if let Err(record) = decode::<Execution>("execution", &execution, &data) {
unreadable.push(record);
}
}
Ok(unreadable)
}
pub async fn read_run(state: &Path, id: &str) -> Result<RunView, String> {
let id: RunId = id.parse()?;
snapshot(state, move |db, state| {
let run = saved_run(db, state, &id)?;
let (requests, mut unreadable) = saved_requests(db, &id)?;
unreadable.extend(unreadable_executions(db, &id)?);
unreadable.sort_by(|a, b| (&a.kind, &a.id).cmp(&(&b.kind, &b.id)));
Ok(RunView {
run,
requests: requests.into_iter().map(std::sync::Arc::new).collect(),
unreadable,
})
})
.await
}
pub(crate) async fn read_run_record(state: &Path, id: &RunId) -> Result<Run, String> {
let id = id.clone();
snapshot(state, move |db, state| saved_run(db, state, &id)).await
}
pub(crate) async fn read_run_requests_only(
state: &Path,
id: &RunId,
) -> Result<Vec<Request>, String> {
let id = id.clone();
snapshot(state, move |db, _| Ok(saved_requests(db, &id)?.0)).await
}
pub(crate) async fn read_new_results(
state: &Path,
id: &RunId,
seen: BTreeMap<RequestId, RequestStatus>,
) -> Result<Vec<Request>, String> {
let id = id.clone();
snapshot(state, move |db, _| {
let changed: Vec<RequestId> = {
let mut statement =
db.prepare("SELECT id,status FROM requests WHERE run_id=? ORDER BY ordinal")?;
let saved = statement
.query_map([&id], |row| {
Ok((row.get::<_, RequestId>(0)?, row.get::<_, RequestStatus>(1)?))
})?
.collect::<Result<Vec<_>, _>>()?;
saved
.into_iter()
.filter(|(request, status)| finished(*status) && seen.get(request) != Some(status))
.map(|(request, _)| request)
.collect()
};
let mut statement = db.prepare("SELECT data FROM requests WHERE id=?")?;
let mut requests = Vec::new();
for request in changed {
let data: String = statement.query_row([&request], |row| row.get(0))?;
match decode::<Request>("request", request.as_str(), &data) {
Ok(request) => requests.push(request),
Err(record) => skipped(&record),
}
}
Ok(requests)
})
.await
}
fn finished(status: RequestStatus) -> bool {
matches!(
status,
RequestStatus::Provisional
| RequestStatus::Green
| RequestStatus::Red
| RequestStatus::Error
)
}