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