use std::collections::{BTreeMap, BTreeSet};
use perspective_js::utils::*;
use crate::config::{DerivedTableConfig, OptionalUpdate, ViewerConfigUpdate};
use crate::derived::topo_order;
use crate::presentation::Presentation;
use crate::session::MissingTable;
use crate::tasks::{RestoreErrors, RestoreMode, build_derived, restore_panel};
use crate::workspace::Workspace;
pub async fn apply_derived_tables(
workspace: &Workspace,
configs: BTreeMap<String, DerivedTableConfig>,
) -> ApiResult<()> {
let order = topo_order(&configs).map_err(ApiError::new)?;
let reuse = workspace.derived_reusable(&order, &configs);
for name in order.iter().rev() {
if reuse.contains(name) || !workspace.is_derived(name) {
continue;
}
let consumers = workspace.derived_consumers(name);
if consumers > 0 {
return Err(ApiError::new(format!(
"Cannot redefine `{name}` while {consumers} panels or tables read it"
)));
}
if let Some(stale) = workspace.take_derived(name) {
stale.delete().await?;
}
}
let mut deferred = BTreeMap::new();
for name in &order {
let config = &configs[name];
if reuse.contains(name) {
continue;
}
if deferred.contains_key(&config.table) {
deferred.insert(name.clone(), config.clone());
continue;
}
let Some(client) = workspace
.resolve_client_for_table(&config.table, None)
.await
else {
deferred.insert(name.clone(), config.clone());
continue;
};
if !build_derived(&client, workspace, name, &config.table, &config.view_config).await? {
deferred.insert(name.clone(), config.clone());
}
}
workspace.set_derived_pending(deferred);
Ok(())
}
pub async fn prune_derived(workspace: &Workspace, keep: &BTreeSet<String>) -> ApiResult<()> {
let mut doomed = workspace
.derived_names()
.into_iter()
.filter(|name| !keep.contains(name))
.collect::<Vec<_>>();
doomed.sort_by_key(|name| std::cmp::Reverse(workspace.derived_ancestors(name).len()));
for name in doomed {
if workspace.derived_consumers(&name) > 0 {
tracing::warn!("Keeping derived table \"{name}\", which is still read");
continue;
}
if let Some(derived) = workspace.take_derived(&name)
&& let Err(err) = derived.delete().await
{
tracing::warn!("Failed to delete derived table \"{name}\": {err}");
}
}
Ok(())
}
pub async fn resume_derived(workspace: &Workspace) -> ApiResult<()> {
let pending = workspace.derived_pending();
if pending.is_empty() {
return Ok(());
}
let mut configs = workspace.derived_configs();
configs.extend(pending);
apply_derived_tables(workspace, configs).await
}
pub async fn purge_derived(workspace: &Workspace, presentation: &Presentation) -> ApiResult<()> {
let pending = workspace.derived_pending();
if workspace.derived_names().is_empty() && pending.is_empty() {
return Ok(());
}
let parent_of = |name: &str| {
workspace
.derived_config(name)
.map(|x| x.table)
.or_else(|| pending.get(name).map(|x| x.table.clone()))
};
let root_of = |name: &str| {
let mut seen = BTreeSet::new();
let mut cursor = name.to_owned();
while seen.insert(cursor.clone()) {
match parent_of(&cursor) {
Some(parent) => cursor = parent,
None => break,
}
}
cursor
};
let active = workspace.active_id();
let mut rebinds = Vec::new();
for panel in workspace.panels() {
let Some(bound) = panel
.session
.get_table()
.map(|x| x.get_name().to_owned())
.or_else(|| panel.session.pending_table())
else {
continue;
};
if parent_of(&bound).is_none() {
continue;
}
let update = ViewerConfigUpdate {
table: OptionalUpdate::Update(root_of(&bound).into()),
..ViewerConfigUpdate::default()
};
let is_active = active.as_ref() == Some(&panel.id);
let presentation = presentation.clone();
let workspace = workspace.clone();
rebinds.push(async move {
restore_panel(
&panel.session,
&panel.renderer,
&presentation,
&workspace,
RestoreMode::Existing { active: is_active },
update,
RestoreErrors::Publish,
MissingTable::Pend,
None,
)
.await
});
}
futures::future::join_all(rebinds)
.await
.into_iter()
.collect::<ApiResult<Vec<_>>>()?;
workspace.set_derived_pending(BTreeMap::new());
prune_derived(workspace, &BTreeSet::new()).await
}