1use std::{collections::HashMap, path::Path, sync::Arc};
2
3use serde::Serialize;
4
5use rusqlite::OptionalExtension;
6use tokio_rusqlite::Connection;
7
8use super::{
9 DATABASE, Execution, HumanClaim, Request,
10 definitions::Graph,
11 receipts::{Error, check_files, schema_initialized},
12};
13use crate::workspace::{canonical_target, outside_workspace};
14
15#[derive(Debug, Clone, Serialize)]
19pub struct RequestView {
20 #[serde(flatten)]
21 pub request: Arc<Request>,
22 pub claim: Option<HumanClaim>,
23 pub execution: Option<Arc<Execution>>,
24 pub definition: Option<RequestDefinition>,
25}
26
27#[derive(Clone)]
29pub struct RequestDefinition(Place);
30#[derive(Clone)]
31enum Place {
32 Graph {
34 graph: Arc<Graph>,
35 eval: usize,
37 artifact: crate::types::ArtifactName,
38 },
39 Own {
42 eval: Arc<super::definitions::Eval>,
43 artifact: Arc<super::definitions::Artifact>,
44 },
45}
46impl RequestDefinition {
47 pub fn eval(&self) -> &super::definitions::Eval {
48 match &self.0 {
49 Place::Graph { graph, eval, .. } => &graph.evals()[*eval],
50 Place::Own { eval, .. } => eval,
51 }
52 }
53 pub fn artifact(&self) -> &super::definitions::Artifact {
54 match &self.0 {
55 Place::Graph {
56 graph, artifact, ..
57 } => graph
58 .artifact(artifact)
59 .expect("an indexed request's Artifact is in its graph"),
60 Place::Own { artifact, .. } => artifact,
61 }
62 }
63}
64impl Serialize for RequestDefinition {
65 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
66 use serde::ser::SerializeStruct;
67 let mut definition = serializer.serialize_struct("RequestDefinition", 2)?;
68 definition.serialize_field("eval", self.eval())?;
69 definition.serialize_field("artifact", self.artifact())?;
70 definition.end()
71 }
72}
73impl std::fmt::Debug for RequestDefinition {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("RequestDefinition")
76 .field("eval", self.eval())
77 .field("artifact", self.artifact())
78 .finish()
79 }
80}
81
82#[derive(Default)]
83struct Filter<'a> {
84 run: Option<&'a str>,
85 id: Option<&'a str>,
86 repo: Option<&'a Path>,
87 waiting: bool,
88 session: Option<&'a str>,
89 statuses: &'a [crate::types::RequestStatus],
90 slice: Option<(u64, u32)>,
92 shown: Shown,
93}
94
95#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
97pub enum Shown {
98 #[default]
101 Definitions,
102 Requests,
104}
105
106pub async fn read_requests(state: &Path, run: Option<&str>) -> Result<Vec<RequestView>, String> {
107 read(
108 state,
109 Filter {
110 run,
111 ..Filter::default()
112 },
113 )
114 .await
115}
116
117pub async fn read_requests_in(
118 state: &Path,
119 run: Option<&str>,
120 repo: Option<&Path>,
121) -> Result<Vec<RequestView>, String> {
122 read(
123 state,
124 Filter {
125 run,
126 repo,
127 ..Filter::default()
128 },
129 )
130 .await
131}
132
133pub async fn read_request(state: &Path, id: &str) -> Result<RequestView, String> {
134 read_request_as(state, id, Shown::Definitions).await
135}
136
137pub async fn read_request_as(state: &Path, id: &str, shown: Shown) -> Result<RequestView, String> {
139 read(
140 state,
141 Filter {
142 id: Some(id),
143 shown,
144 ..Filter::default()
145 },
146 )
147 .await?
148 .pop()
149 .ok_or_else(|| "Review request not found.".into())
150}
151
152pub async fn read_session_request(state: &Path, id: &str) -> Result<Option<RequestView>, String> {
154 Ok(read(
155 state,
156 Filter {
157 session: Some(id),
158 ..Filter::default()
159 },
160 )
161 .await?
162 .pop())
163}
164
165pub async fn read_original(state: &Path, view: &RequestView) -> Result<RequestView, String> {
169 read_original_as(state, view, Shown::Definitions).await
170}
171
172pub async fn read_original_as(
174 state: &Path,
175 view: &RequestView,
176 shown: Shown,
177) -> Result<RequestView, String> {
178 let original = view
179 .execution
180 .as_ref()
181 .map(|execution| &execution.provenance.request_id)
182 .or_else(|| {
183 view.request
184 .provenance
185 .as_ref()
186 .map(|source| &source.request_id)
187 });
188 match original.filter(|id| *id != &view.request.id) {
189 Some(id) => read_request_as(state, id, shown).await,
190 None => Ok(view.clone()),
191 }
192}
193
194pub async fn read_waiting_originals(
197 state: &Path,
198 repo: Option<&Path>,
199) -> Result<Vec<RequestView>, String> {
200 let waiting = read_waiting(state, repo).await?;
201 let by_id: std::collections::BTreeMap<_, _> = waiting
202 .iter()
203 .map(|view| (&view.request.id, view))
204 .collect();
205 let mut seen = std::collections::BTreeSet::new();
206 let mut originals: Vec<RequestView> = Vec::with_capacity(waiting.len());
207 for view in &waiting {
208 let source = view
209 .execution
210 .as_ref()
211 .map_or(&view.request.id, |execution| {
212 &execution.provenance.request_id
213 });
214 if seen.contains(source) {
215 continue;
216 }
217 let original = match by_id.get(source) {
218 Some(original) => Some((*original).clone()),
219 None => read_original(state, view).await.ok(),
220 };
221 let original = original
223 .filter(|original| original.request.status().is_human_wait())
224 .unwrap_or_else(|| view.clone());
225 if seen.insert(original.request.id.clone()) {
226 originals.push(original);
227 }
228 }
229 Ok(originals)
230}
231
232pub async fn read_request_batch(
236 state: &Path,
237 statuses: &[crate::types::RequestStatus],
238 offset: u64,
239 limit: u32,
240 shown: Shown,
241) -> Result<Vec<(RequestView, std::path::PathBuf)>, String> {
242 read_with_repo(
243 state,
244 Filter {
245 statuses,
246 slice: Some((offset, limit)),
247 shown,
248 ..Filter::default()
249 },
250 )
251 .await
252}
253
254pub async fn read_waiting(state: &Path, repo: Option<&Path>) -> Result<Vec<RequestView>, String> {
258 read(
259 state,
260 Filter {
261 repo,
262 waiting: true,
263 shown: Shown::Requests,
264 ..Filter::default()
265 },
266 )
267 .await
268}
269
270async fn read(state: &Path, filter: Filter<'_>) -> Result<Vec<RequestView>, String> {
271 Ok(read_with_repo(state, filter)
272 .await?
273 .into_iter()
274 .map(|(view, _)| view)
275 .collect())
276}
277
278pub async fn read_request_repo(state: &Path, id: &str) -> Result<std::path::PathBuf, String> {
282 let not_found = || "Review request not found.".to_owned();
283 let id: crate::types::RequestId = id.parse()?;
284 let state = canonical_target(state).map_err(|e| e.to_string())?;
285 let Some(connection) = open_read(&state).await? else {
286 return Err(not_found());
287 };
288 let result = connection
289 .call(move |db| -> Result<_, Error> {
290 super::wait_when_busy(db)?;
291 let transaction = db.transaction()?;
292 if !schema_initialized(&transaction)? {
293 return Ok(None);
294 }
295 let repo = transaction
296 .query_row(
297 "SELECT r.repo FROM requests q JOIN runs r ON r.id=q.run_id WHERE q.id=?",
298 [&id],
299 |row| row.get::<_, String>(0),
300 )
301 .optional()?
302 .map(std::path::PathBuf::from);
303 transaction.commit()?;
304 if let Some(repo) = &repo {
305 crate::workspace::outside_workspaces(std::iter::once(repo.as_path()), &state)
306 .map_err(|error| Error::Invalid(error.to_string()))?;
307 }
308 Ok(repo)
309 })
310 .await
311 .map_err(|e| e.to_string());
312 super::connection::finish(connection, result)
313 .await?
314 .ok_or_else(not_found)
315}
316
317pub(super) async fn open_read(state: &Path) -> Result<Option<Connection>, String> {
319 check_files(state)?;
320 if !state
321 .join(DATABASE)
322 .try_exists()
323 .map_err(|e| e.to_string())?
324 {
325 return Ok(None);
326 }
327 Connection::open_with_flags(
328 state.join(DATABASE),
329 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
330 )
331 .await
332 .map(Some)
333 .map_err(|e| e.to_string())
334}
335
336async fn read_with_repo(
337 state: &Path,
338 filter: Filter<'_>,
339) -> Result<Vec<(RequestView, std::path::PathBuf)>, String> {
340 let state = canonical_target(state).map_err(|e| e.to_string())?;
341 let repo = filter
342 .repo
343 .map(canonical_target)
344 .transpose()
345 .map_err(|e| e.to_string())?;
346 if let Some(repo) = &repo {
347 outside_workspace(repo, &state).map_err(|e| e.to_string())?;
348 }
349 let run = filter
350 .run
351 .map(str::parse::<crate::types::RunId>)
352 .transpose()?;
353 let id = filter
354 .id
355 .map(str::parse::<crate::types::RequestId>)
356 .transpose()?;
357 let repo = repo.map(|repo| crate::platform::path_text(&repo));
358 let waiting = filter.waiting;
359 let statuses = filter.statuses.to_vec();
360 let slice = filter.slice;
361 let session = filter
362 .session
363 .map(str::parse::<crate::types::SessionId>)
364 .transpose()?;
365 let shown = filter.shown;
366 let Some(connection) = open_read(&state).await? else {
367 return Ok(Vec::new());
368 };
369 let result = connection
370 .call(move |db| -> Result<_, Error> {
371 super::wait_when_busy(db)?;
372 let transaction = db.transaction()?;
373 if !schema_initialized(&transaction)? {
374 return Ok(Vec::new());
375 }
376 let filter = Query {
377 run,
378 id,
379 repo,
380 waiting,
381 session,
382 statuses,
383 slice,
384 ids: None,
385 };
386 let rows = rows(&transaction, &filter)?;
387 let ids: std::collections::BTreeSet<_> = rows.iter().map(|row| &row.run).collect();
388 let mut statement = transaction.prepare(match shown {
389 Shown::Definitions => "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(?))",
390 Shown::Requests => "SELECT r.id,r.repo,NULL,NULL FROM runs r WHERE r.id IN (SELECT value FROM json_each(?))",
391 })?;
392 let runs = statement
393 .query_map([serde_json::to_string(&ids)?], |row| {
394 Ok((
395 row.get::<_, crate::types::RunId>(0)?,
396 row.get::<_, String>(1)?,
397 row.get::<_, Option<String>>(2)?,
398 row.get::<_, Option<String>>(3)?,
399 ))
400 })?
401 .collect::<Result<Vec<_>, _>>()?;
402 let mut indexes = std::collections::BTreeMap::new();
403 let mut repos = Vec::new();
404 for (id, repo, state, definitions) in runs {
405 let index = match state {
406 None => DefinitionIndex::none(),
407 Some(state) => {
408 match super::run_record::whole(&id, &state, definitions.as_deref()) {
409 Ok(run) => DefinitionIndex::apart(&run),
410 Err(unreadable) => {
411 super::unreadable::skipped(&unreadable);
412 continue;
413 }
414 }
415 }
416 };
417 let repo = std::path::PathBuf::from(repo);
418 repos.push(repo.clone());
419 indexes.insert(id, (index, repo));
420 }
421 crate::workspace::outside_workspaces(repos.iter().map(|repo| repo.as_path()), &state)
422 .map_err(|error| Error::Invalid(error.to_string()))?;
423 let views = rows
424 .into_iter()
425 .filter_map(|row| {
426 indexes
427 .get_mut(&row.run)
428 .and_then(|(index, repo)| row.view(index).map(|view| (view, repo.clone())))
429 })
430 .collect();
431 drop(statement);
432 transaction.commit()?;
433 Ok(views)
434 })
435 .await
436 .map_err(|e| e.to_string());
437 super::connection::finish(connection, result).await
438}
439
440#[derive(Default)]
442pub(super) struct Query {
443 pub run: Option<crate::types::RunId>,
444 pub id: Option<crate::types::RequestId>,
445 pub repo: Option<String>,
446 pub waiting: bool,
447 pub session: Option<crate::types::SessionId>,
448 pub statuses: Vec<crate::types::RequestStatus>,
449 pub slice: Option<(u64, u32)>,
450 pub ids: Option<Vec<crate::types::RequestId>>,
452}
453
454#[derive(Debug, Clone)]
455pub(super) struct Row {
456 pub id: crate::types::RequestId,
457 pub run: crate::types::RunId,
458 pub eval: crate::types::EvalId,
459 pub data: String,
460 pub execution: Option<String>,
461 pub execution_id: Option<crate::types::ExecutionId>,
462 pub claim: Option<(
463 crate::types::RequestId,
464 crate::types::ReviewerId,
465 crate::types::Timestamp,
466 )>,
467}
468
469pub(super) fn rows(db: &rusqlite::Connection, filter: &Query) -> Result<Vec<Row>, Error> {
470 let mut predicates = Vec::new();
471 let mut values: Vec<rusqlite::types::Value> = Vec::new();
472 for (column, value) in [
473 ("q.run_id", filter.run.as_ref().map(ToString::to_string)),
474 ("q.id", filter.id.as_ref().map(ToString::to_string)),
475 ("r.repo", filter.repo.clone()),
476 (
477 "json_extract(q.data,'$.sessionId')",
478 filter.session.as_ref().map(ToString::to_string),
479 ),
480 ] {
481 if let Some(value) = value {
482 predicates.push(format!("{column}=?"));
483 values.push(value.into());
484 }
485 }
486 if filter.waiting {
487 predicates.push("q.status IN ('WAITING_HUMAN','DEFERRED')".into());
488 }
489 if let Some(ids) = &filter.ids {
490 predicates.push("q.id IN (SELECT value FROM json_each(?))".into());
491 values.push(serde_json::to_string(ids)?.into());
492 }
493 if !filter.statuses.is_empty() {
494 let marks = vec!["?"; filter.statuses.len()].join(",");
495 predicates.push(format!("q.status IN ({marks})"));
496 values.extend(
497 filter
498 .statuses
499 .iter()
500 .map(|status| rusqlite::types::Value::from(status.as_str().to_owned())),
501 );
502 }
503 let predicate = if predicates.is_empty() {
504 String::new()
505 } else {
506 format!("WHERE {}", predicates.join(" AND "))
507 };
508 let limit = match filter.slice {
509 Some((offset, count)) => {
510 values.push(i64::from(count).into());
511 values.push(i64::try_from(offset).unwrap_or(i64::MAX).into());
512 " LIMIT ? OFFSET ?"
513 }
514 None => "",
515 };
516 let order = if filter.run.is_some() {
517 "q.ordinal"
518 } else {
519 "r.rowid DESC,q.ordinal"
520 };
521 let mut statement = db.prepare_cached(&format!(
522 "SELECT q.id,q.run_id,q.data,e.data,h.id,h.claimed_by,h.claimed_at,q.eval_id,e.id
523 FROM requests q JOIN runs r ON r.id=q.run_id
524 LEFT JOIN executions e ON e.id=q.execution_id
525 LEFT JOIN requests h ON h.id=json_extract(e.data,'$.provenance.requestId') AND h.claimed_by IS NOT NULL
526 {predicate} ORDER BY {order}{limit}"
527 ))?;
528 Ok(statement
529 .query_map(rusqlite::params_from_iter(values), |row| {
530 let id = row.get::<_, Option<crate::types::RequestId>>(4)?;
531 let reviewer = row.get::<_, Option<crate::types::ReviewerId>>(5)?;
532 let at = row.get::<_, Option<crate::types::Timestamp>>(6)?;
533 Ok(Row {
534 id: row.get(0)?,
535 run: row.get(1)?,
536 eval: row.get(7)?,
537 data: row.get(2)?,
538 execution: row.get(3)?,
539 execution_id: row.get(8)?,
540 claim: id
541 .zip(reviewer)
542 .zip(at)
543 .map(|((id, reviewer), at)| (id, reviewer, at)),
544 })
545 })?
546 .collect::<Result<_, _>>()?)
547}
548
549pub(super) struct DefinitionIndex {
553 graph: Option<Arc<Graph>>,
554 evals: HashMap<crate::types::EvalId, usize>,
555 copies: Option<Copies>,
557}
558#[derive(Default)]
559struct Copies {
560 evals: HashMap<usize, Arc<super::definitions::Eval>>,
561 artifacts: HashMap<crate::types::ArtifactName, Arc<super::definitions::Artifact>>,
562}
563impl DefinitionIndex {
564 pub fn new(run: &super::Run) -> Self {
566 let graph = run.definitions.share();
567 let mut evals = HashMap::new();
568 for (position, eval) in graph.iter().flat_map(|graph| graph.evals()).enumerate() {
569 evals.entry(eval.id.clone()).or_insert(position);
570 }
571 Self {
572 graph,
573 evals,
574 copies: None,
575 }
576 }
577 fn apart(run: &super::Run) -> Self {
580 Self {
581 copies: Some(Copies::default()),
582 ..Self::new(run)
583 }
584 }
585 fn none() -> Self {
587 Self {
588 graph: None,
589 evals: HashMap::new(),
590 copies: None,
591 }
592 }
593 fn definition(
594 &mut self,
595 eval: &crate::types::EvalId,
596 artifact: &crate::types::ArtifactName,
597 ) -> Option<RequestDefinition> {
598 let graph = self.graph.as_ref()?;
599 let saved = graph.artifact(artifact)?;
600 let position = *self.evals.get(eval)?;
601 Some(RequestDefinition(match &mut self.copies {
602 None => Place::Graph {
603 graph: graph.clone(),
604 eval: position,
605 artifact: artifact.clone(),
606 },
607 Some(copies) => Place::Own {
608 eval: copies
609 .evals
610 .entry(position)
611 .or_insert_with(|| Arc::new(graph.evals()[position].clone()))
612 .clone(),
613 artifact: copies
614 .artifacts
615 .entry(artifact.clone())
616 .or_insert_with(|| Arc::new(saved.clone()))
617 .clone(),
618 },
619 }))
620 }
621}
622impl Row {
623 pub fn view(&self, index: &mut DefinitionIndex) -> Option<RequestView> {
624 let request = super::unreadable::evidence::<Request>("request", &self.data)?;
625 let execution = self
626 .execution
627 .as_deref()
628 .and_then(|data| super::unreadable::evidence("execution", data));
629 Some(self.with_evidence(index, request, execution))
630 }
631 pub fn read(
634 &self,
635 index: &mut DefinitionIndex,
636 ) -> (Option<RequestView>, Vec<super::Unreadable>) {
637 let mut unreadable = Vec::new();
638 let execution = self
639 .execution
640 .as_deref()
641 .zip(self.execution_id.as_ref())
642 .and_then(|(data, id)| {
643 super::unreadable::decode("execution", id, data)
644 .map_err(|record| unreadable.push(record))
645 .ok()
646 });
647 let view = super::unreadable::decode::<Request>("request", &self.id, &self.data)
648 .map_err(|record| unreadable.push(record))
649 .ok()
650 .map(|request| self.with_evidence(index, request, execution));
651 (view, unreadable)
652 }
653 fn with_evidence(
654 &self,
655 index: &mut DefinitionIndex,
656 request: Request,
657 execution: Option<Execution>,
658 ) -> RequestView {
659 let definition = index.definition(&self.eval, &request.target);
660 RequestView {
661 request: Arc::new(request),
662 definition,
663 execution: execution.map(Arc::new),
664 claim: self.claim.as_ref().map(|(id, reviewer, at)| HumanClaim {
665 request_id: id.clone(),
666 reviewer: reviewer.clone(),
667 claimed_at: *at,
668 }),
669 }
670 }
671}
672
673pub async fn read_run_requests(
675 state: &Path,
676 id: &str,
677) -> Result<(super::RunView, Vec<RequestView>), String> {
678 let state = state.to_path_buf();
679 let id: crate::types::RunId = id.parse()?;
680 crate::task::joined(
681 tokio::task::spawn_blocking(move || {
682 let mut reader = super::reader::Reader::default();
683 reader
684 .snapshot(&state, |db, cache, state| {
685 cache.selected(db, state, &id, true)
686 })?
687 .ok_or_else(|| "Run not found.".into())
688 })
689 .await,
690 )
691 .map_err(|error| error.to_string())?
692}