Skip to main content

reshape/migrations/
mod.rs

1use crate::{
2    db::{Conn, Transaction},
3    schema::Schema,
4};
5use core::fmt::Debug;
6use serde::{Deserialize, Serialize};
7
8/// Validate a complete SQL statement using pg_query
9pub fn validate_sql_statement(sql: &str) -> Result<(), String> {
10    pg_query::parse(sql).map(|_| ()).map_err(|e| e.to_string())
11}
12
13/// Validate an SQL expression by wrapping it in SELECT
14pub fn validate_sql_expression(expr: &str) -> Result<(), String> {
15    let wrapped = format!("SELECT ({})", expr);
16    pg_query::parse(&wrapped).map(|_| ()).map_err(|e| e.to_string())
17}
18
19// Re-export migration types
20mod common;
21pub use common::Column;
22
23mod create_table;
24pub use create_table::CreateTable;
25
26mod alter_column;
27pub use alter_column::{AlterColumn, ColumnChanges};
28
29mod add_column;
30pub use add_column::AddColumn;
31
32mod remove_column;
33pub use remove_column::RemoveColumn;
34
35mod add_index;
36pub use add_index::{AddIndex, Index};
37
38mod remove_index;
39pub use remove_index::RemoveIndex;
40
41mod remove_table;
42pub use remove_table::RemoveTable;
43
44mod rename_table;
45pub use rename_table::RenameTable;
46
47mod create_enum;
48pub use create_enum::CreateEnum;
49
50mod remove_enum;
51pub use remove_enum::RemoveEnum;
52
53mod custom;
54pub use custom::Custom;
55
56mod add_foreign_key;
57pub use add_foreign_key::AddForeignKey;
58
59mod remove_foreign_key;
60pub use remove_foreign_key::RemoveForeignKey;
61
62#[derive(Serialize, Deserialize, Debug)]
63pub struct Migration {
64    pub name: String,
65    pub description: Option<String>,
66    pub actions: Vec<Box<dyn Action>>,
67}
68
69impl Migration {
70    pub fn new(name: impl Into<String>, description: Option<String>) -> Migration {
71        Migration {
72            name: name.into(),
73            description,
74            actions: vec![],
75        }
76    }
77
78    pub fn with_action(mut self, action: impl Action + 'static) -> Self {
79        self.actions.push(Box::new(action));
80        self
81    }
82}
83
84impl PartialEq for Migration {
85    fn eq(&self, other: &Self) -> bool {
86        self.name == other.name
87    }
88}
89
90impl Eq for Migration {}
91
92impl Clone for Migration {
93    fn clone(&self) -> Self {
94        let serialized = serde_json::to_string(self).unwrap();
95        serde_json::from_str(&serialized).unwrap()
96    }
97}
98
99pub struct MigrationContext {
100    migration_index: usize,
101    action_index: usize,
102    existing_schema_name: Option<String>,
103}
104
105impl MigrationContext {
106    pub fn new(
107        migration_index: usize,
108        action_index: usize,
109        existing_schema_name: Option<String>,
110    ) -> Self {
111        MigrationContext {
112            migration_index,
113            action_index,
114            existing_schema_name,
115        }
116    }
117
118    fn prefix(&self) -> String {
119        format!(
120            "__reshape_{:0>4}_{:0>4}",
121            self.migration_index, self.action_index
122        )
123    }
124
125    fn prefix_inverse(&self) -> String {
126        format!(
127            "__reshape_{:0>4}_{:0>4}",
128            1000 - self.migration_index,
129            1000 - self.action_index
130        )
131    }
132}
133
134#[typetag::serde(tag = "type")]
135pub trait Action: Debug {
136    fn describe(&self) -> String;
137    fn run(&self, ctx: &MigrationContext, db: &mut dyn Conn, schema: &Schema)
138        -> anyhow::Result<()>;
139    fn complete<'a>(
140        &self,
141        ctx: &MigrationContext,
142        db: &'a mut dyn Conn,
143    ) -> anyhow::Result<Option<Transaction<'a>>>;
144    fn update_schema(&self, ctx: &MigrationContext, schema: &mut Schema);
145    fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()>;
146
147    /// Validate user-provided SQL. Returns list of (field_name, sql, error_message).
148    fn validate_sql(&self) -> Vec<(String, String, String)> {
149        vec![] // Default: no SQL to validate
150    }
151}