Skip to main content

schema_sync/
planner.rs

1//! Developer: s4gor
2//! Github: https://github.com/s4gor
3//!
4//! Migration planning
5//!
6//! The planner takes a schema diff and creates an executable migration plan.
7//! The plan is a sequence of operations that will transform the current
8//! schema into the target schema.
9//!
10//! ## Design Rationale
11//!
12//! Separating planning from execution allows:
13//! - Dry-run mode to show what would happen
14//! - Validation of plans before execution
15//! - Different planning strategies (safe ordering, dependency resolution)
16//! - Testing plans without executing them
17
18use async_trait::async_trait;
19use serde::{Deserialize, Serialize};
20
21use crate::diff::SchemaDiff;
22use crate::errors::Result;
23use crate::snapshot::SchemaSnapshot;
24
25/// A migration plan containing the sequence of operations to execute
26///
27/// The plan is ordered and dependency-aware. Operations are grouped
28/// into steps that can be executed in order.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct MigrationPlan {
31    /// Steps to execute in order
32    pub steps: Vec<MigrationStep>,
33
34    /// Estimated duration (in seconds)
35    pub estimated_duration_secs: f64,
36
37    /// Whether this plan requires downtime
38    pub requires_downtime: bool,
39
40    /// Warnings about potentially dangerous operations
41    pub warnings: Vec<String>,
42}
43
44/// A single step in a migration plan
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct MigrationStep {
47    /// Step number (for ordering)
48    pub step_number: usize,
49
50    /// Operation to perform
51    pub operation: MigrationOperation,
52
53    /// Description of what this step does
54    pub description: String,
55
56    /// Whether this step can be rolled back
57    pub reversible: bool,
58
59    /// Dependencies on other steps (must complete before this step)
60    pub dependencies: Vec<usize>,
61}
62
63/// A single migration operation
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(tag = "type")]
66pub enum MigrationOperation {
67    /// Create a new table
68    CreateTable {
69        table_name: String,
70        columns: Vec<ColumnDefinition>,
71        primary_key: Option<PrimaryKeyDefinition>,
72        indexes: Vec<IndexDefinition>,
73    },
74
75    /// Drop a table
76    DropTable {
77        table_name: String,
78    },
79
80    /// Add a column to a table
81    AddColumn {
82        table_name: String,
83        column: ColumnDefinition,
84    },
85
86    /// Drop a column from a table
87    DropColumn {
88        table_name: String,
89        column_name: String,
90    },
91
92    /// Modify a column (type, nullability, default, etc.)
93    ModifyColumn {
94        table_name: String,
95        column_name: String,
96        changes: ColumnChanges,
97    },
98
99    /// Add a foreign key constraint
100    AddForeignKey {
101        table_name: String,
102        constraint: ForeignKeyDefinition,
103    },
104
105    /// Drop a foreign key constraint
106    DropForeignKey {
107        table_name: String,
108        constraint_name: String,
109    },
110
111    /// Add a unique constraint
112    AddUniqueConstraint {
113        table_name: String,
114        constraint: UniqueConstraintDefinition,
115    },
116
117    /// Drop a unique constraint
118    DropUniqueConstraint {
119        table_name: String,
120        constraint_name: String,
121    },
122
123    /// Create an index
124    CreateIndex {
125        table_name: String,
126        index: IndexDefinition,
127    },
128
129    /// Drop an index
130    DropIndex {
131        table_name: String,
132        index_name: String,
133    },
134
135    /// Add a check constraint
136    AddCheckConstraint {
137        table_name: String,
138        constraint: CheckConstraintDefinition,
139    },
140
141    /// Drop a check constraint
142    DropCheckConstraint {
143        table_name: String,
144        constraint_name: String,
145    },
146
147    /// Create a view
148    CreateView {
149        view_name: String,
150        definition: String,
151    },
152
153    /// Drop a view
154    DropView {
155        view_name: String,
156    },
157
158    /// Modify a view (recreate)
159    ModifyView {
160        view_name: String,
161        new_definition: String,
162    },
163
164    /// Create a function
165    CreateFunction {
166        function_name: String,
167        signature: String,
168        body: String,
169        return_type: String,
170    },
171
172    /// Drop a function
173    DropFunction {
174        function_name: String,
175        signature: String,
176    },
177
178    /// Create a type
179    CreateType {
180        type_name: String,
181        kind: String,
182        definition: String,
183    },
184
185    /// Drop a type
186    DropType {
187        type_name: String,
188    },
189}
190
191/// Column definition for migration operations
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct ColumnDefinition {
194    pub name: String,
195    pub data_type: String,
196    pub nullable: bool,
197    pub default_value: Option<String>,
198    pub auto_increment: bool,
199}
200
201/// Primary key definition
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203pub struct PrimaryKeyDefinition {
204    pub name: String,
205    pub columns: Vec<String>,
206}
207
208/// Foreign key definition
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
210pub struct ForeignKeyDefinition {
211    pub name: String,
212    pub columns: Vec<String>,
213    pub referenced_table: String,
214    pub referenced_columns: Vec<String>,
215    pub on_delete: Option<String>,
216    pub on_update: Option<String>,
217}
218
219/// Unique constraint definition
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
221pub struct UniqueConstraintDefinition {
222    pub name: String,
223    pub columns: Vec<String>,
224}
225
226/// Index definition
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct IndexDefinition {
229    pub name: String,
230    pub columns: Vec<String>,
231    pub unique: bool,
232    pub index_type: Option<String>,
233}
234
235/// Check constraint definition
236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
237pub struct CheckConstraintDefinition {
238    pub name: String,
239    pub expression: String,
240}
241
242/// Column changes for modify operations
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244pub struct ColumnChanges {
245    pub data_type: Option<TypeChange>,
246    pub nullable: Option<bool>,
247    pub default_value: Option<DefaultChange>,
248}
249
250/// Type change (old and new)
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252pub struct TypeChange {
253    pub old_type: String,
254    pub new_type: String,
255}
256
257/// Default value change (old and new)
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259pub struct DefaultChange {
260    pub old_default: Option<String>,
261    pub new_default: Option<String>,
262}
263
264/// Trait for planning migrations
265///
266/// Different planners can implement different strategies:
267/// - Safe ordering (create before references)
268/// - Dependency resolution
269/// - Conflict detection
270/// - Optimization (batch operations)
271#[async_trait]
272pub trait Planner: Send + Sync {
273    /// Create a migration plan from a schema diff
274    ///
275    /// # Arguments
276    ///
277    /// * `current` - Current schema snapshot
278    /// * `target` - Target schema snapshot
279    /// * `diff` - Calculated diff between current and target
280    ///
281    /// # Returns
282    ///
283    /// A migration plan that will transform current into target.
284    async fn create_plan(
285        &self,
286        current: &SchemaSnapshot,
287        target: &SchemaSnapshot,
288        diff: &SchemaDiff,
289    ) -> Result<MigrationPlan>;
290
291    /// Validate a migration plan
292    ///
293    /// Checks for:
294    /// - Circular dependencies
295    /// - Invalid operations
296    /// - Safety issues
297    async fn validate_plan(&self, plan: &MigrationPlan) -> Result<()>;
298}
299