use crate::adapters::{DatabaseAdapter, MigrationRunner, SchemaInspector};
use crate::diff::{DiffCalculator, SchemaDiff};
use crate::errors::Result;
use crate::executor::Executor;
use crate::planner::Planner;
use crate::snapshot::{SchemaSnapshot, SnapshotStore};
#[derive(Debug, Clone)]
pub struct SyncResult {
pub already_in_sync: bool,
pub diff: SchemaDiff,
pub changes_applied: usize,
pub execution_result: Option<crate::executor::ExecutionResult>,
}
pub struct Engine {
inspector: Box<dyn SchemaInspector>,
runner: Box<dyn MigrationRunner>,
planner: Box<dyn Planner>,
executor: Box<dyn Executor>,
diff_calculator: Box<dyn DiffCalculator>,
snapshot_store: Option<Box<dyn SnapshotStore>>,
}
impl Engine {
pub fn new(
inspector: Box<dyn SchemaInspector>,
runner: Box<dyn MigrationRunner>,
planner: Box<dyn Planner>,
executor: Box<dyn Executor>,
diff_calculator: Box<dyn DiffCalculator>,
snapshot_store: Option<Box<dyn SnapshotStore>>,
) -> Self {
Self {
inspector,
runner,
planner,
executor,
diff_calculator,
snapshot_store,
}
}
pub fn from_adapter(
adapter: Box<dyn DatabaseAdapter>,
planner: Box<dyn Planner>,
executor: Box<dyn Executor>,
diff_calculator: Box<dyn DiffCalculator>,
snapshot_store: Option<Box<dyn SnapshotStore>>,
) -> Self {
Self::new(
adapter.inspector(),
adapter.migration_runner(),
planner,
executor,
diff_calculator,
snapshot_store,
)
}
pub async fn sync_tenant(
&self,
tenant: &crate::cli::TenantContext,
target: Option<&SchemaSnapshot>,
execute: bool,
) -> Result<SyncResult> {
let current = self.inspector.inspect_schema(tenant).await?;
let target = match target {
Some(t) => t.clone(),
None => {
match &self.snapshot_store {
Some(store) => {
store.get_latest(tenant).await?
.ok_or_else(|| crate::errors::Error::Snapshot(
format!("No target snapshot found for tenant {}", tenant.id())
))?
}
None => {
return Err(crate::errors::Error::Snapshot(
"No target snapshot provided and no snapshot store configured".to_string()
));
}
}
}
};
let diff = self.diff_calculator.calculate_diff(¤t, &target);
if diff.is_empty() {
return Ok(SyncResult {
already_in_sync: true,
diff,
changes_applied: 0,
execution_result: None,
});
}
let plan = self.planner.create_plan(¤t, &target, &diff).await?;
self.planner.validate_plan(&plan).await?;
let execution_result = if execute {
Some(self.executor.execute(tenant, &plan, self.runner.as_ref()).await?)
} else {
Some(self.executor.dry_run(tenant, &plan, self.runner.as_ref()).await?)
};
Ok(SyncResult {
already_in_sync: false,
diff,
changes_applied: execution_result.as_ref()
.map(|r| r.steps_executed)
.unwrap_or(0),
execution_result,
})
}
pub async fn inspect_tenant(&self, tenant: &crate::cli::TenantContext) -> Result<SchemaSnapshot> {
self.inspector.inspect_schema(tenant).await
}
pub fn calculate_diff(&self, from: &SchemaSnapshot, to: &SchemaSnapshot) -> SchemaDiff {
self.diff_calculator.calculate_diff(from, to)
}
pub async fn list_tenants(&self) -> Result<Vec<crate::cli::TenantContext>> {
self.inspector.list_tenants().await
}
}