swarm / companies/starter/work/project/src/sync/migration_runner.rs

raw file 2177 bytes
```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<String>,
    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<DryRunReport> {
        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<DryRunReport> {
        // 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<DryRunReport> {
        // 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()?