```rust use anyhow::{Result, Context}; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DryRunReport { pub dry_run: bool, pub records_to_insert: u64, pub records_to_update: u64, pub records_to_delete: u64, pub validation_errors: Vec, pub estimated_duration_secs: u64, pub completed_at: String, } pub struct MigrationRunner { dry_run: bool, source_conn: String, target_conn: String, } impl MigrationRunner { pub fn new(source_conn: &str, target_conn: &str, dry_run: bool) -> Self { Self { dry_run, source_conn: source_conn.to_string(), target_conn: target_conn.to_string(), } } pub fn execute(&self) -> Result { let start = Instant::now(); if self.dry_run { self.run_dry() } else { self.run_production() } .map(|mut report| { let elapsed = start.elapsed(); report.estimated_duration_secs = elapsed.as_secs(); report.completed_at = chrono::Utc::now().to_rfc3339(); report }) } fn run_dry(&self) -> Result { // Simulate the migration by querying counts and validating constraints // without writing to the target database. let inserts = self.count_records_to_insert()?; let updates = self.count_records_to_update()?; let deletes = self.count_records_to_delete()?; let errors = self.validate_constraints()?; Ok(DryRunReport { dry_run: true, records_to_insert: inserts, records_to_update: updates, records_to_delete: deletes, validation_errors: errors, estimated_duration_secs: 0, completed_at: String::new(), }) } fn run_production(&self) -> Result { // Actual migration execution (not shown in full for brevity, // but follows the same count logic before executing writes) let inserts = self.count_records_to_insert()?