#![allow(dead_code)]
use std::path::Path;
use anyhow::{Result, anyhow};
use tempfile::TempDir;
use tokio_postgres::Client;
use pgevolve::executor::{ApplyError, ApplyOverrides};
use pgevolve::pg_querier::PgCatalogQuerier;
use pgevolve_core::catalog::{CatalogFilter, read_catalog};
use pgevolve_core::identifier::Identifier;
use pgevolve_core::ir::catalog::Catalog;
use pgevolve_core::plan::{
Plan, PlannerPolicy, Strategy, group_steps, order, rewrite, write_plan_dir,
};
pub async fn connect_and_bootstrap(pg: &pgevolve_testkit::EphemeralPostgres) -> Result<Client> {
let mut client = pg.connect().await?;
pgevolve::executor::bootstrap_metadata(&mut client).await?;
create_generator_role_pool(&client).await?;
Ok(client)
}
async fn create_generator_role_pool(client: &Client) -> Result<()> {
const ROLES: &[&str] = &["app_owner", "readers", "writers", "app", "ops", "auditor"];
for r in ROLES {
let sql = format!(
"DO $do$ BEGIN \
CREATE ROLE {r}; \
EXCEPTION WHEN duplicate_object THEN NULL; END $do$;"
);
client
.batch_execute(&sql)
.await
.map_err(|e| anyhow!("pre-create role {r}: {e}"))?;
}
Ok(())
}
pub async fn build_plan(
client: &Client,
target: &Catalog,
source: &Catalog,
dir: &Path,
) -> Result<Plan> {
let identity = pgevolve::compute_target_identity(client).await?;
let changes = pgevolve_core::diff::diff(
target,
source,
&pgevolve_core::catalog::DriftReport::default(),
);
let policy = PlannerPolicy {
strategy: Strategy::Online,
..PlannerPolicy::default()
};
let ordered =
order(target, source, changes, &policy).map_err(|e| anyhow!("plan order: {e}"))?;
let steps = rewrite(ordered, target, &policy);
let groups = group_steps(steps);
let plan = Plan::from_grouped(
groups,
source,
target,
identity,
None,
pgevolve_core::VERSION,
policy.planner_ruleset_version,
)
.map_err(|e| anyhow!("from_grouped: {e}"))?;
write_plan_dir(&plan, dir)?;
Ok(plan)
}
pub fn catalog_filter(managed_schemas: &[Identifier]) -> Result<CatalogFilter> {
CatalogFilter::new(managed_schemas.to_vec(), vec![]).map_err(|e| anyhow!(e))
}
pub async fn apply_diff(
client: &mut Client,
target: &Catalog,
source: &Catalog,
managed_schemas: &[Identifier],
abort_after_step: Option<u32>,
) -> Result<Result<pgevolve::executor::ApplyOutcome, ApplyError>> {
let dir = TempDir::new()?;
let _plan = build_plan(client, target, source, dir.path()).await?;
let filter = catalog_filter(managed_schemas)?;
let overrides = ApplyOverrides {
allow_different_target: false,
allow_drift: true, allow_unwaived_lint: true, allow_unapproved_intents: true, actor: Some("chaos-harness".into()),
abort_after_step,
};
Ok(pgevolve::apply(dir.path(), client, &filter, overrides).await)
}
pub fn assert_convergent(live: &Catalog, source: &Catalog) -> Result<()> {
let changeset = pgevolve_core::diff::diff(
live,
source,
&pgevolve_core::catalog::DriftReport::default(),
);
if changeset.is_empty() {
return Ok(());
}
pgevolve_testkit::assert_canonical_eq(source, live)
}
pub async fn introspect(
pg: &pgevolve_testkit::EphemeralPostgres,
managed_schemas: &[Identifier],
) -> Result<Catalog> {
let client = pg.connect().await?;
let querier = PgCatalogQuerier::new(client)?;
let filter = catalog_filter(managed_schemas)?;
let (catalog, _drift) = tokio::task::spawn_blocking(move || read_catalog(&querier, &filter))
.await
.map_err(|e| anyhow!("join: {e}"))?
.map_err(|e| anyhow!("read_catalog: {e}"))?;
Ok(catalog)
}
pub fn schemas_of(catalog: &Catalog) -> Vec<Identifier> {
catalog.schemas.iter().map(|s| s.name.clone()).collect()
}