schema-sync 1.0.0

Production-grade schema synchronization for multi-tenant databases
Documentation
//! Developer: s4gor
//! Github: https://github.com/s4gor
//!
//! Migration planning
//!
//! The planner takes a schema diff and creates an executable migration plan.
//! The plan is a sequence of operations that will transform the current
//! schema into the target schema.
//!
//! ## Design Rationale
//!
//! Separating planning from execution allows:
//! - Dry-run mode to show what would happen
//! - Validation of plans before execution
//! - Different planning strategies (safe ordering, dependency resolution)
//! - Testing plans without executing them

use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use crate::diff::SchemaDiff;
use crate::errors::Result;
use crate::snapshot::SchemaSnapshot;

/// A migration plan containing the sequence of operations to execute
///
/// The plan is ordered and dependency-aware. Operations are grouped
/// into steps that can be executed in order.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MigrationPlan {
    /// Steps to execute in order
    pub steps: Vec<MigrationStep>,

    /// Estimated duration (in seconds)
    pub estimated_duration_secs: f64,

    /// Whether this plan requires downtime
    pub requires_downtime: bool,

    /// Warnings about potentially dangerous operations
    pub warnings: Vec<String>,
}

/// A single step in a migration plan
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MigrationStep {
    /// Step number (for ordering)
    pub step_number: usize,

    /// Operation to perform
    pub operation: MigrationOperation,

    /// Description of what this step does
    pub description: String,

    /// Whether this step can be rolled back
    pub reversible: bool,

    /// Dependencies on other steps (must complete before this step)
    pub dependencies: Vec<usize>,
}

/// A single migration operation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum MigrationOperation {
    /// Create a new table
    CreateTable {
        table_name: String,
        columns: Vec<ColumnDefinition>,
        primary_key: Option<PrimaryKeyDefinition>,
        indexes: Vec<IndexDefinition>,
    },

    /// Drop a table
    DropTable {
        table_name: String,
    },

    /// Add a column to a table
    AddColumn {
        table_name: String,
        column: ColumnDefinition,
    },

    /// Drop a column from a table
    DropColumn {
        table_name: String,
        column_name: String,
    },

    /// Modify a column (type, nullability, default, etc.)
    ModifyColumn {
        table_name: String,
        column_name: String,
        changes: ColumnChanges,
    },

    /// Add a foreign key constraint
    AddForeignKey {
        table_name: String,
        constraint: ForeignKeyDefinition,
    },

    /// Drop a foreign key constraint
    DropForeignKey {
        table_name: String,
        constraint_name: String,
    },

    /// Add a unique constraint
    AddUniqueConstraint {
        table_name: String,
        constraint: UniqueConstraintDefinition,
    },

    /// Drop a unique constraint
    DropUniqueConstraint {
        table_name: String,
        constraint_name: String,
    },

    /// Create an index
    CreateIndex {
        table_name: String,
        index: IndexDefinition,
    },

    /// Drop an index
    DropIndex {
        table_name: String,
        index_name: String,
    },

    /// Add a check constraint
    AddCheckConstraint {
        table_name: String,
        constraint: CheckConstraintDefinition,
    },

    /// Drop a check constraint
    DropCheckConstraint {
        table_name: String,
        constraint_name: String,
    },

    /// Create a view
    CreateView {
        view_name: String,
        definition: String,
    },

    /// Drop a view
    DropView {
        view_name: String,
    },

    /// Modify a view (recreate)
    ModifyView {
        view_name: String,
        new_definition: String,
    },

    /// Create a function
    CreateFunction {
        function_name: String,
        signature: String,
        body: String,
        return_type: String,
    },

    /// Drop a function
    DropFunction {
        function_name: String,
        signature: String,
    },

    /// Create a type
    CreateType {
        type_name: String,
        kind: String,
        definition: String,
    },

    /// Drop a type
    DropType {
        type_name: String,
    },
}

/// Column definition for migration operations
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColumnDefinition {
    pub name: String,
    pub data_type: String,
    pub nullable: bool,
    pub default_value: Option<String>,
    pub auto_increment: bool,
}

/// Primary key definition
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrimaryKeyDefinition {
    pub name: String,
    pub columns: Vec<String>,
}

/// Foreign key definition
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForeignKeyDefinition {
    pub name: String,
    pub columns: Vec<String>,
    pub referenced_table: String,
    pub referenced_columns: Vec<String>,
    pub on_delete: Option<String>,
    pub on_update: Option<String>,
}

/// Unique constraint definition
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UniqueConstraintDefinition {
    pub name: String,
    pub columns: Vec<String>,
}

/// Index definition
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexDefinition {
    pub name: String,
    pub columns: Vec<String>,
    pub unique: bool,
    pub index_type: Option<String>,
}

/// Check constraint definition
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckConstraintDefinition {
    pub name: String,
    pub expression: String,
}

/// Column changes for modify operations
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColumnChanges {
    pub data_type: Option<TypeChange>,
    pub nullable: Option<bool>,
    pub default_value: Option<DefaultChange>,
}

/// Type change (old and new)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeChange {
    pub old_type: String,
    pub new_type: String,
}

/// Default value change (old and new)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DefaultChange {
    pub old_default: Option<String>,
    pub new_default: Option<String>,
}

/// Trait for planning migrations
///
/// Different planners can implement different strategies:
/// - Safe ordering (create before references)
/// - Dependency resolution
/// - Conflict detection
/// - Optimization (batch operations)
#[async_trait]
pub trait Planner: Send + Sync {
    /// Create a migration plan from a schema diff
    ///
    /// # Arguments
    ///
    /// * `current` - Current schema snapshot
    /// * `target` - Target schema snapshot
    /// * `diff` - Calculated diff between current and target
    ///
    /// # Returns
    ///
    /// A migration plan that will transform current into target.
    async fn create_plan(
        &self,
        current: &SchemaSnapshot,
        target: &SchemaSnapshot,
        diff: &SchemaDiff,
    ) -> Result<MigrationPlan>;

    /// Validate a migration plan
    ///
    /// Checks for:
    /// - Circular dependencies
    /// - Invalid operations
    /// - Safety issues
    async fn validate_plan(&self, plan: &MigrationPlan) -> Result<()>;
}