1use std::{path::Path, sync::Arc};
2
3use serde::Serialize;
4
5use tokio_rusqlite::Connection;
6
7use super::{
8 DATABASE, Execution, HumanClaim, Request,
9 receipts::{Error, check_files, schema_initialized},
10};
11use crate::workspace::{canonical_target, outside_workspace};
12
13#[derive(Debug, Clone, Serialize)]
14pub struct RequestView {
15 #[serde(flatten)]
16 pub request: Request,
17 pub claim: Option<HumanClaim>,
18 pub execution: Option<Execution>,
19 pub definition: Option<RequestDefinition>,
20}
21
22#[derive(Debug, Clone, Serialize)]
24pub struct RequestDefinition {
25 pub eval: Arc<super::definitions::Eval>,
26 pub artifact: Arc<super::definitions::Artifact>,
27}
28
29#[derive(Default)]
30struct Filter<'a> {
31 run: Option<&'a str>,
32 id: Option<&'a str>,
33 repo: Option<&'a Path>,
34 waiting: bool,
35 session: Option<&'a str>,
36 statuses: &'a [crate::types::RequestStatus],
37 slice: Option<(u64, u32)>,
39}
40
41pub async fn read_requests(state: &Path, run: Option<&str>) -> Result<Vec<RequestView>, String> {
42 read(
43 state,
44 Filter {
45 run,
46 ..Filter::default()
47 },
48 )
49 .await
50}
51
52pub async fn read_requests_in(
53 state: &Path,
54 run: Option<&str>,
55 repo: Option<&Path>,
56) -> Result<Vec<RequestView>, String> {
57 read(
58 state,
59 Filter {
60 run,
61 repo,
62 ..Filter::default()
63 },
64 )
65 .await
66}
67
68pub async fn read_request(state: &Path, id: &str) -> Result<RequestView, String> {
69 read(
70 state,
71 Filter {
72 id: Some(id),
73 ..Filter::default()
74 },
75 )
76 .await?
77 .pop()
78 .ok_or_else(|| "Review request not found.".into())
79}
80
81pub async fn read_session_request(state: &Path, id: &str) -> Result<Option<RequestView>, String> {
83 Ok(read(
84 state,
85 Filter {
86 session: Some(id),
87 ..Filter::default()
88 },
89 )
90 .await?
91 .pop())
92}
93
94pub async fn read_original(state: &Path, view: &RequestView) -> Result<RequestView, String> {
98 let original = view
99 .execution
100 .as_ref()
101 .map(|execution| &execution.provenance.request_id)
102 .or_else(|| {
103 view.request
104 .provenance
105 .as_ref()
106 .map(|source| &source.request_id)
107 });
108 match original.filter(|id| *id != &view.request.id) {
109 Some(id) => read_request(state, id).await,
110 None => Ok(view.clone()),
111 }
112}
113
114pub async fn read_waiting_originals(
117 state: &Path,
118 repo: Option<&Path>,
119) -> Result<Vec<RequestView>, String> {
120 let waiting = read_waiting(state, repo).await?;
121 let by_id: std::collections::BTreeMap<_, _> = waiting
122 .iter()
123 .map(|view| (&view.request.id, view))
124 .collect();
125 let mut seen = std::collections::BTreeSet::new();
126 let mut originals: Vec<RequestView> = Vec::with_capacity(waiting.len());
127 for view in &waiting {
128 let source = view
129 .execution
130 .as_ref()
131 .map_or(&view.request.id, |execution| {
132 &execution.provenance.request_id
133 });
134 if seen.contains(source) {
135 continue;
136 }
137 let original = match by_id.get(source) {
138 Some(original) => Some((*original).clone()),
139 None => read_original(state, view).await.ok(),
140 };
141 let original = original
143 .filter(|original| original.request.status().is_human_wait())
144 .unwrap_or_else(|| view.clone());
145 if seen.insert(original.request.id.clone()) {
146 originals.push(original);
147 }
148 }
149 Ok(originals)
150}
151
152pub async fn read_request_batch(
155 state: &Path,
156 statuses: &[crate::types::RequestStatus],
157 offset: u64,
158 limit: u32,
159) -> Result<Vec<(RequestView, std::path::PathBuf)>, String> {
160 read_with_repo(
161 state,
162 Filter {
163 statuses,
164 slice: Some((offset, limit)),
165 ..Filter::default()
166 },
167 )
168 .await
169}
170
171pub async fn read_waiting(state: &Path, repo: Option<&Path>) -> Result<Vec<RequestView>, String> {
173 read(
174 state,
175 Filter {
176 repo,
177 waiting: true,
178 ..Filter::default()
179 },
180 )
181 .await
182}
183
184async fn read(state: &Path, filter: Filter<'_>) -> Result<Vec<RequestView>, String> {
185 Ok(read_with_repo(state, filter)
186 .await?
187 .into_iter()
188 .map(|(view, _)| view)
189 .collect())
190}
191
192pub async fn read_request_repo(
194 state: &Path,
195 id: &str,
196) -> Result<(RequestView, std::path::PathBuf), String> {
197 read_with_repo(
198 state,
199 Filter {
200 id: Some(id),
201 ..Filter::default()
202 },
203 )
204 .await?
205 .pop()
206 .ok_or_else(|| "Review request not found.".into())
207}
208async fn read_with_repo(
209 state: &Path,
210 filter: Filter<'_>,
211) -> Result<Vec<(RequestView, std::path::PathBuf)>, String> {
212 let state = canonical_target(state).map_err(|e| e.to_string())?;
213 let repo = filter
214 .repo
215 .map(canonical_target)
216 .transpose()
217 .map_err(|e| e.to_string())?;
218 if let Some(repo) = &repo {
219 outside_workspace(repo, &state).map_err(|e| e.to_string())?;
220 }
221 check_files(&state)?;
222 if !state
223 .join(DATABASE)
224 .try_exists()
225 .map_err(|e| e.to_string())?
226 {
227 return Ok(Vec::new());
228 }
229 let run = filter
230 .run
231 .map(str::parse::<crate::types::RunId>)
232 .transpose()?;
233 let id = filter
234 .id
235 .map(str::parse::<crate::types::RequestId>)
236 .transpose()?;
237 let repo = repo.map(|repo| crate::platform::path_text(&repo));
238 let waiting = filter.waiting;
239 let statuses = filter.statuses.to_vec();
240 let slice = filter.slice;
241 let session = filter
242 .session
243 .map(str::parse::<crate::types::SessionId>)
244 .transpose()?;
245 let connection = Connection::open_with_flags(
246 state.join(DATABASE),
247 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
248 )
249 .await
250 .map_err(|e| e.to_string())?;
251 let result = connection
252 .call(move |db| -> Result<_, Error> {
253 db.busy_timeout(super::SQLITE_BUSY_TIMEOUT)?;
254 let transaction = db.transaction()?;
255 if !schema_initialized(&transaction)? {
256 return Ok(Vec::new());
257 }
258 let filter = Query {
259 run,
260 id,
261 repo,
262 waiting,
263 session,
264 statuses,
265 slice,
266 ids: None,
267 };
268 let rows = rows(&transaction, &filter)?;
269 let ids: std::collections::BTreeSet<_> = rows.iter().map(|row| &row.run).collect();
270 let mut statement = transaction.prepare(
271 "SELECT r.id,r.repo,r.data,d.data FROM runs r LEFT JOIN run_definitions d ON d.run_id=r.id WHERE r.id IN (SELECT value FROM json_each(?))",
272 )?;
273 let runs = statement
274 .query_map([serde_json::to_string(&ids)?], |row| {
275 Ok((
276 row.get::<_, crate::types::RunId>(0)?,
277 row.get::<_, String>(1)?,
278 row.get::<_, String>(2)?,
279 row.get::<_, Option<String>>(3)?,
280 ))
281 })?
282 .collect::<Result<Vec<_>, _>>()?;
283 let mut indexes = std::collections::BTreeMap::new();
284 let mut repos = Vec::new();
285 for (id, repo, state, definitions) in runs {
286 let run = match super::run_record::whole(&id, &state, definitions.as_deref()) {
287 Ok(run) => run,
288 Err(unreadable) => {
289 super::unreadable::skipped(&unreadable);
290 continue;
291 }
292 };
293 repos.push(std::path::PathBuf::from(repo));
294 indexes.insert(id, (DefinitionIndex::new(&run), run.repo_path));
295 }
296 crate::workspace::outside_workspaces(repos.iter().map(|repo| repo.as_path()), &state)
297 .map_err(|error| Error::Invalid(error.to_string()))?;
298 let views = rows
299 .into_iter()
300 .filter_map(|row| {
301 indexes
302 .get(&row.run)
303 .and_then(|(index, repo)| row.view(index).map(|view| (view, repo.clone())))
304 })
305 .collect();
306 drop(statement);
307 transaction.commit()?;
308 Ok(views)
309 })
310 .await
311 .map_err(|e| e.to_string());
312 super::connection::finish(connection, result).await
313}
314
315#[derive(Default)]
317pub(super) struct Query {
318 pub run: Option<crate::types::RunId>,
319 pub id: Option<crate::types::RequestId>,
320 pub repo: Option<String>,
321 pub waiting: bool,
322 pub session: Option<crate::types::SessionId>,
323 pub statuses: Vec<crate::types::RequestStatus>,
324 pub slice: Option<(u64, u32)>,
325 pub ids: Option<Vec<crate::types::RequestId>>,
327}
328
329#[derive(Debug, Clone)]
330pub(super) struct Row {
331 pub id: crate::types::RequestId,
332 pub run: crate::types::RunId,
333 pub eval: crate::types::EvalId,
334 pub data: String,
335 pub execution: Option<String>,
336 pub execution_id: Option<crate::types::ExecutionId>,
337 pub claim: Option<(
338 crate::types::RequestId,
339 crate::types::ReviewerId,
340 crate::types::Timestamp,
341 )>,
342}
343
344pub(super) fn rows(db: &rusqlite::Connection, filter: &Query) -> Result<Vec<Row>, Error> {
345 let mut predicates = Vec::new();
346 let mut values: Vec<rusqlite::types::Value> = Vec::new();
347 for (column, value) in [
348 ("q.run_id", filter.run.as_ref().map(ToString::to_string)),
349 ("q.id", filter.id.as_ref().map(ToString::to_string)),
350 ("r.repo", filter.repo.clone()),
351 (
352 "json_extract(q.data,'$.sessionId')",
353 filter.session.as_ref().map(ToString::to_string),
354 ),
355 ] {
356 if let Some(value) = value {
357 predicates.push(format!("{column}=?"));
358 values.push(value.into());
359 }
360 }
361 if filter.waiting {
362 predicates.push("q.status IN ('WAITING_HUMAN','DEFERRED')".into());
363 }
364 if let Some(ids) = &filter.ids {
365 predicates.push("q.id IN (SELECT value FROM json_each(?))".into());
366 values.push(serde_json::to_string(ids)?.into());
367 }
368 if !filter.statuses.is_empty() {
369 let marks = vec!["?"; filter.statuses.len()].join(",");
370 predicates.push(format!("q.status IN ({marks})"));
371 values.extend(
372 filter
373 .statuses
374 .iter()
375 .map(|status| rusqlite::types::Value::from(status.as_str().to_owned())),
376 );
377 }
378 let predicate = if predicates.is_empty() {
379 String::new()
380 } else {
381 format!("WHERE {}", predicates.join(" AND "))
382 };
383 let limit = match filter.slice {
384 Some((offset, count)) => {
385 values.push(i64::from(count).into());
386 values.push(i64::try_from(offset).unwrap_or(i64::MAX).into());
387 " LIMIT ? OFFSET ?"
388 }
389 None => "",
390 };
391 let order = if filter.run.is_some() {
392 "q.ordinal"
393 } else {
394 "r.rowid DESC,q.ordinal"
395 };
396 let mut statement = db.prepare_cached(&format!(
397 "SELECT q.id,q.run_id,q.data,e.data,h.id,h.claimed_by,h.claimed_at,q.eval_id,e.id
398 FROM requests q JOIN runs r ON r.id=q.run_id
399 LEFT JOIN executions e ON e.id=q.execution_id
400 LEFT JOIN requests h ON h.id=json_extract(e.data,'$.provenance.requestId') AND h.claimed_by IS NOT NULL
401 {predicate} ORDER BY {order}{limit}"
402 ))?;
403 Ok(statement
404 .query_map(rusqlite::params_from_iter(values), |row| {
405 let id = row.get::<_, Option<crate::types::RequestId>>(4)?;
406 let reviewer = row.get::<_, Option<crate::types::ReviewerId>>(5)?;
407 let at = row.get::<_, Option<crate::types::Timestamp>>(6)?;
408 Ok(Row {
409 id: row.get(0)?,
410 run: row.get(1)?,
411 eval: row.get(7)?,
412 data: row.get(2)?,
413 execution: row.get(3)?,
414 execution_id: row.get(8)?,
415 claim: id
416 .zip(reviewer)
417 .zip(at)
418 .map(|((id, reviewer), at)| (id, reviewer, at)),
419 })
420 })?
421 .collect::<Result<_, _>>()?)
422}
423
424pub(super) struct DefinitionIndex {
427 artifacts:
428 std::collections::BTreeMap<crate::types::ArtifactName, Arc<super::definitions::Artifact>>,
429 evals: std::collections::BTreeMap<crate::types::EvalId, Arc<super::definitions::Eval>>,
430}
431impl DefinitionIndex {
432 pub fn new(run: &super::Run) -> Self {
433 let mut evals = std::collections::BTreeMap::new();
434 if let Some(graph) = run.definitions.graph() {
435 for eval in graph.evals() {
436 evals
437 .entry(eval.id.clone())
438 .or_insert_with(|| Arc::new(eval.clone()));
439 }
440 }
441 Self {
442 artifacts: run
443 .definitions
444 .graph()
445 .map(|graph| {
446 graph
447 .artifacts()
448 .map(|(id, artifact)| (id.clone(), Arc::new(artifact.clone())))
449 .collect()
450 })
451 .unwrap_or_default(),
452 evals,
453 }
454 }
455}
456impl Row {
457 pub fn view(&self, index: &DefinitionIndex) -> Option<RequestView> {
458 let request = super::unreadable::evidence::<Request>("request", &self.data)?;
459 let execution = self
460 .execution
461 .as_deref()
462 .and_then(|data| super::unreadable::evidence("execution", data));
463 Some(self.with_evidence(index, request, execution))
464 }
465 pub fn read(&self, index: &DefinitionIndex) -> (Option<RequestView>, Vec<super::Unreadable>) {
468 let mut unreadable = Vec::new();
469 let execution = self
470 .execution
471 .as_deref()
472 .zip(self.execution_id.as_ref())
473 .and_then(|(data, id)| {
474 super::unreadable::decode("execution", id, data)
475 .map_err(|record| unreadable.push(record))
476 .ok()
477 });
478 let view = super::unreadable::decode::<Request>("request", &self.id, &self.data)
479 .map_err(|record| unreadable.push(record))
480 .ok()
481 .map(|request| self.with_evidence(index, request, execution));
482 (view, unreadable)
483 }
484 fn with_evidence(
485 &self,
486 index: &DefinitionIndex,
487 request: Request,
488 execution: Option<Execution>,
489 ) -> RequestView {
490 let definition = index
491 .evals
492 .get(&self.eval)
493 .zip(index.artifacts.get(&request.target))
494 .map(|(eval, artifact)| RequestDefinition {
495 eval: eval.clone(),
496 artifact: artifact.clone(),
497 });
498 RequestView {
499 request,
500 definition,
501 execution,
502 claim: self.claim.as_ref().map(|(id, reviewer, at)| HumanClaim {
503 request_id: id.clone(),
504 reviewer: reviewer.clone(),
505 claimed_at: *at,
506 }),
507 }
508 }
509}
510
511pub async fn read_run_requests(
513 state: &Path,
514 id: &str,
515) -> Result<(super::RunView, Vec<RequestView>), String> {
516 let state = state.to_path_buf();
517 let id: crate::types::RunId = id.parse()?;
518 crate::task::joined(
519 tokio::task::spawn_blocking(move || {
520 let mut reader = super::reader::Reader::default();
521 reader
522 .snapshot(&state, |db, cache, state| {
523 cache.selected(db, state, &id, true)
524 })?
525 .ok_or_else(|| "Run not found.".into())
526 })
527 .await,
528 )
529 .map_err(|error| error.to_string())?
530}