1use crate::{Executor, OrmResult};
2
3pub trait Migration {
5 fn name(&self) -> &'static str;
7 fn up(&self, executor: &mut dyn Executor) -> OrmResult<()>;
9 fn down(&self, executor: &mut dyn Executor) -> OrmResult<()>;
11}
12
13#[derive(Debug, Clone)]
15pub struct MigrationStatus {
16 pub name: String,
17 pub applied: bool,
18}
19
20pub struct MigrationManager;
22
23impl MigrationManager {
24 pub fn ensure_migrations_table(executor: &mut dyn Executor) -> OrmResult<()> {
26 let sql = r#"
27 CREATE TABLE IF NOT EXISTS __chopin_migrations (
28 name TEXT PRIMARY KEY,
29 applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
30 )
31 "#;
32 executor.execute(sql, &[])?;
33 Ok(())
34 }
35
36 pub fn status(
38 executor: &mut dyn Executor,
39 migrations: &[&dyn Migration],
40 ) -> OrmResult<Vec<MigrationStatus>> {
41 Self::ensure_migrations_table(executor)?;
42
43 let mut statuses = Vec::with_capacity(migrations.len());
44 for m in migrations {
45 let name = m.name();
46 let check_sql = "SELECT 1 FROM __chopin_migrations WHERE name = $1";
47 let rows = executor.query(check_sql, &[&name])?;
48 statuses.push(MigrationStatus {
49 name: name.to_string(),
50 applied: !rows.is_empty(),
51 });
52 }
53 Ok(statuses)
54 }
55
56 pub fn up(executor: &mut dyn Executor, migrations: &[&dyn Migration]) -> OrmResult<()> {
58 Self::ensure_migrations_table(executor)?;
59
60 for m in migrations {
61 let name = m.name();
62 let check_sql = "SELECT 1 FROM __chopin_migrations WHERE name = $1";
63 let rows = executor.query(check_sql, &[&name])?;
64
65 if rows.is_empty() {
66 #[cfg(feature = "log")]
67 log::info!("Applying migration: {}", name);
68 m.up(executor)?;
69 let insert_sql = "INSERT INTO __chopin_migrations (name) VALUES ($1)";
70 executor.execute(insert_sql, &[&name])?;
71 #[cfg(feature = "log")]
72 log::info!("Successfully applied: {}", name);
73 }
74 }
75 Ok(())
76 }
77
78 pub fn down(executor: &mut dyn Executor, migrations: &[&dyn Migration]) -> OrmResult<()> {
80 Self::ensure_migrations_table(executor)?;
81
82 for m in migrations.iter().rev() {
83 let name = m.name();
84 let check_sql = "SELECT 1 FROM __chopin_migrations WHERE name = $1";
85 let rows = executor.query(check_sql, &[&name])?;
86
87 if !rows.is_empty() {
88 #[cfg(feature = "log")]
89 log::info!("Reverting migration: {}", name);
90 m.down(executor)?;
91 let delete_sql = "DELETE FROM __chopin_migrations WHERE name = $1";
92 executor.execute(delete_sql, &[&name])?;
93 #[cfg(feature = "log")]
94 log::info!("Successfully reverted: {}", name);
95 }
96 }
97 Ok(())
98 }
99}
100
101pub struct Index {
103 pub name: &'static str,
104 pub columns: &'static [&'static str],
105 pub unique: bool,
106}