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