use std::path::Path;
use tokio_postgres::Client;
use pgevolve_core::plan::Plan;
use crate::cluster_config::ClusterConfig;
use crate::executor::{
ApplyError, ApplyOutcome, ApplyOverrides,
cluster_preflight::{ClusterPreflightOverrides, run_cluster_preflight},
lock::{release_lock, try_acquire_lock},
};
pub async fn apply_cluster_plan(
plan: &Plan,
client: &mut Client,
overrides: ApplyOverrides,
) -> Result<ApplyOutcome, ApplyError> {
crate::executor::bootstrap::bootstrap_metadata(client).await?;
let actor = overrides
.actor
.clone()
.unwrap_or_else(crate::executor::default_actor);
try_acquire_lock(client, &actor).await?;
let cluster_overrides = ClusterPreflightOverrides {
allow_different_target: overrides.allow_different_target,
allow_unapproved_intents: overrides.allow_unapproved_intents,
};
let preflight_result = run_cluster_preflight(client, plan, cluster_overrides).await;
if let Err(e) = preflight_result {
let _ = release_lock(client).await;
return Err(e);
}
let apply_id = crate::executor::audit::open_apply_log(client, plan, &actor).await?;
let exec_result =
crate::executor::execute::execute_plan(client, plan, apply_id, overrides.abort_after_step)
.await;
match exec_result {
Ok(()) => {
crate::executor::audit::close_apply_log(client, apply_id, "succeeded", None).await?;
release_lock(client).await?;
Ok(ApplyOutcome::Succeeded { apply_id })
}
Err(ApplyError::AbortedAfterStep { step_no }) => {
crate::executor::audit::close_apply_log(
client,
apply_id,
"aborted",
Some(&format!("abort_after_step={step_no}")),
)
.await?;
let _ = release_lock(client).await;
Err(ApplyError::AbortedAfterStep { step_no })
}
Err(e) => {
let msg = e.to_string();
crate::executor::audit::close_apply_log(client, apply_id, "failed", Some(&msg)).await?;
let _ = release_lock(client).await;
Err(e)
}
}
}
pub async fn apply_cluster_plan_dir(
plan_dir: &Path,
cfg: &ClusterConfig,
) -> Result<(), ClusterApplyError> {
let plan = pgevolve_core::plan::read_plan_dir(plan_dir)
.map_err(|e| ClusterApplyError::PlanLoad(e.to_string()))?;
let (mut client, connection) =
tokio_postgres::connect(&cfg.connection.dsn, tokio_postgres::NoTls)
.await
.map_err(|e| ClusterApplyError::Connection(e.to_string()))?;
tokio::spawn(async move {
if let Err(err) = connection.await {
tracing::debug!(?err, "cluster plan-dir apply connection task ended");
}
});
let overrides = crate::executor::ApplyOverrides::default();
apply_cluster_plan(&plan, &mut client, overrides)
.await
.map_err(|e| ClusterApplyError::Apply(e.to_string()))?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum ClusterApplyError {
#[error("connection error: {0}")]
Connection(String),
#[error("step execution failed (sql: {sql:?}): {source}")]
StepFailed {
sql: String,
#[source]
source: tokio_postgres::Error,
},
#[error("plan load error: {0}")]
PlanLoad(String),
#[error("apply error: {0}")]
Apply(String),
}