use super::{Database, DatabaseValue, Error, FilterableQuery, LINEAGE};
#[derive(Debug, Clone)]
pub struct ParentContext {
pub admission: crate::NewWorkflowRun,
pub effective_limits: bcode_workflow::WorkflowRunLimits,
pub parent_link: Option<crate::WorkflowRunLink>,
pub root_run_id: String,
pub depth: u64,
pub revision: u64,
}
pub async fn parent_context(
db: &dyn Database,
owner: &crate::WorkflowExecutionAuthority,
run: &str,
) -> Result<ParentContext, Error> {
let tx = db.begin_transaction().await?;
super::super::verified(&*tx).await?;
let root = super::exact(&*tx, super::RUNS, "run_id", run).await?;
if super::text(&root, "authority_json")?
!= serde_json::to_string(owner).map_err(|_| Error::InvalidAdmission)?
{
return Err(Error::AuthorityConflict);
}
let admission: crate::NewWorkflowRun =
serde_json::from_str(&super::text(&root, "admission_json")?)
.map_err(|_| Error::MaintenanceRequired)?;
if admission.run_id != run {
return Err(Error::MaintenanceRequired);
}
let lineage = super::exact(&*tx, LINEAGE, "run_id", run).await?;
let root_run_id = super::text(&lineage, "root_run_id")?;
let depth = u64::try_from(super::integer(&lineage, "depth")?)
.map_err(|_| Error::MaintenanceRequired)?;
let parent_link = match lineage.get("link_json") {
Some(DatabaseValue::Null)
if depth == 1
&& root_run_id == run
&& lineage.get("parent_run_id") == Some(DatabaseValue::Null) =>
{
None
}
Some(DatabaseValue::String(json)) => {
let link: crate::WorkflowRunLink =
serde_json::from_str(&json).map_err(|_| Error::MaintenanceRequired)?;
if link.child_run_id != run
|| link.root_run_id != root_run_id
|| depth <= 1
|| lineage.get("parent_run_id")
!= Some(DatabaseValue::String(link.parent_run_id.clone()))
{
return Err(Error::MaintenanceRequired);
}
Some(link)
}
_ => return Err(Error::MaintenanceRequired),
};
let effective_limits =
super::super::allowance::effective_limits(&*tx, run, &admission.limits).await?;
let revision = super::super::graph_pages::revision(&*tx, run).await?;
tx.commit().await?;
Ok(ParentContext {
admission,
effective_limits,
parent_link,
root_run_id,
depth,
revision,
})
}
#[derive(Debug, Clone)]
pub struct ChildPage {
pub children: Vec<crate::WorkflowRunLink>,
pub next_child_run_id: Option<String>,
}
pub async fn children(
db: &dyn Database,
parent: &str,
after: Option<&str>,
limit: u16,
) -> Result<ChildPage, Error> {
if !(1..=100).contains(&limit) {
return Err(Error::InvalidAdmission);
}
let tx = db.begin_transaction().await?;
let result = children_in(&*tx, parent, after, limit).await?;
tx.commit().await?;
Ok(result)
}
pub async fn children_in(
tx: &dyn switchy_database::DatabaseTransaction,
parent: &str,
after: Option<&str>,
limit: u16,
) -> Result<ChildPage, Error> {
if !(1..=100).contains(&limit) {
return Err(Error::InvalidAdmission);
}
super::super::verified(tx).await?;
let mut query = tx
.select(LINEAGE)
.columns(&["run_id", "link_json"])
.where_eq("parent_run_id", parent)
.sort("run_id", switchy_database::query::SortDirection::Asc)
.limit(usize::from(limit) + 1);
if let Some(after) = after {
query = query.where_gt("run_id", after);
}
let mut rows = query.execute(tx).await?;
let more = rows.len() > usize::from(limit);
rows.truncate(usize::from(limit));
let mut children = Vec::with_capacity(rows.len());
for row in rows {
let Some(DatabaseValue::String(id)) = row.get("run_id") else {
return Err(Error::MaintenanceRequired);
};
let Some(DatabaseValue::String(json)) = row.get("link_json") else {
return Err(Error::MaintenanceRequired);
};
let link: crate::WorkflowRunLink =
serde_json::from_str(&json).map_err(|_| Error::MaintenanceRequired)?;
if link.parent_run_id != parent || link.child_run_id != id {
return Err(Error::MaintenanceRequired);
}
children.push(link);
}
let next_child_run_id = if more {
children.last().map(|link| link.child_run_id.clone())
} else {
None
};
Ok(ChildPage {
children,
next_child_run_id,
})
}