2021-02-18 12:29:09 +03:00
|
|
|
use crate::config::Config;
|
2021-02-16 18:11:39 +03:00
|
|
|
use crate::database::DatabaseConnection;
|
2021-02-18 12:29:09 +03:00
|
|
|
use crate::databases::DatabaseConnectionManager;
|
2021-02-08 23:42:13 +03:00
|
|
|
use crate::path::PathBuilder;
|
2021-02-14 00:03:37 +03:00
|
|
|
use crate::StdResult;
|
2021-02-18 12:29:09 +03:00
|
|
|
use std::convert::TryFrom;
|
2021-02-08 23:42:13 +03:00
|
|
|
use std::fs;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Migration {
|
|
|
|
upgrade_sql: PathBuf,
|
|
|
|
downgrade_sql: PathBuf,
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Migration {
|
|
|
|
pub(crate) fn new(directory: &PathBuf) -> Option<Migration> {
|
|
|
|
if directory.is_dir() {
|
|
|
|
let name = directory
|
|
|
|
.file_name()
|
|
|
|
.and_then(|name| name.to_str())
|
|
|
|
.unwrap_or_default();
|
|
|
|
let upgrade_sql = PathBuilder::from(directory).append("up.sql").build();
|
|
|
|
let downgrade_sql = PathBuilder::from(directory).append("down.sql").build();
|
|
|
|
|
|
|
|
if upgrade_sql.exists() && downgrade_sql.exists() {
|
|
|
|
return Some(Migration {
|
|
|
|
upgrade_sql,
|
|
|
|
downgrade_sql,
|
|
|
|
name: String::from(name),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2021-02-14 00:03:37 +03:00
|
|
|
}
|
2021-02-08 23:42:13 +03:00
|
|
|
|
2021-02-14 00:03:37 +03:00
|
|
|
impl Migration {
|
2021-02-08 23:42:13 +03:00
|
|
|
pub fn name(&self) -> &String {
|
|
|
|
&self.name
|
|
|
|
}
|
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
fn upgrade_sql_content(&self) -> StdResult<String> {
|
2021-02-08 23:42:13 +03:00
|
|
|
let content = fs::read_to_string(&self.upgrade_sql)?;
|
2021-02-15 13:06:09 +03:00
|
|
|
Ok(content)
|
|
|
|
}
|
2021-02-08 23:42:13 +03:00
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
fn downgrade_sql_content(&self) -> StdResult<String> {
|
|
|
|
let content = fs::read_to_string(&self.downgrade_sql)?;
|
|
|
|
Ok(content)
|
|
|
|
}
|
|
|
|
}
|
2021-02-08 23:42:13 +03:00
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
pub struct MigrationManager {
|
|
|
|
pub(crate) conn: Box<dyn DatabaseConnection>,
|
2021-02-15 13:06:09 +03:00
|
|
|
}
|
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
impl MigrationManager {
|
|
|
|
pub fn new(conn: Box<dyn DatabaseConnection>) -> Self {
|
2021-02-15 13:06:09 +03:00
|
|
|
MigrationManager { conn }
|
2021-02-08 23:42:13 +03:00
|
|
|
}
|
2021-02-14 00:03:37 +03:00
|
|
|
}
|
2021-02-08 23:42:13 +03:00
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
impl TryFrom<&Config> for MigrationManager {
|
|
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
|
|
|
|
fn try_from(config: &Config) -> Result<Self, Self::Error> {
|
|
|
|
let connection_manager = DatabaseConnectionManager::new(&config.database);
|
|
|
|
let conn = connection_manager.connect()?;
|
|
|
|
Ok(Self { conn })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
pub fn is_migrations_table_not_found<D: std::fmt::Display>(error: D) -> bool {
|
|
|
|
error
|
|
|
|
.to_string()
|
|
|
|
.contains(r#"relation "migrations" does not exist"#)
|
|
|
|
}
|
|
|
|
|
2021-02-15 13:47:45 +03:00
|
|
|
pub trait DatabaseMigrationManager {
|
2021-02-15 13:06:09 +03:00
|
|
|
fn apply_sql(&mut self, sql_content: &str) -> StdResult<()>;
|
|
|
|
|
|
|
|
fn create_migrations_table(&mut self) -> StdResult<()>;
|
2021-02-08 23:42:13 +03:00
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
fn insert_migration_info(&mut self, name: &str) -> StdResult<u64>;
|
|
|
|
|
|
|
|
fn delete_migration_info(&mut self, name: &str) -> StdResult<u64>;
|
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
fn applied_migration_names(&mut self) -> StdResult<Vec<String>>;
|
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
fn upgrade(&mut self, migration: &Migration) -> StdResult<()> {
|
|
|
|
let content = migration.upgrade_sql_content()?;
|
|
|
|
|
|
|
|
self.create_migrations_table()?;
|
|
|
|
self.apply_sql(&content)?;
|
|
|
|
self.insert_migration_info(migration.name())?;
|
2021-02-08 23:42:13 +03:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-15 13:06:09 +03:00
|
|
|
|
|
|
|
fn downgrade(&mut self, migration: &Migration) -> StdResult<()> {
|
|
|
|
let content = migration.downgrade_sql_content()?;
|
|
|
|
|
|
|
|
self.apply_sql(&content)?;
|
|
|
|
self.delete_migration_info(migration.name())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
impl DatabaseMigrationManager for MigrationManager {
|
2021-02-15 13:06:09 +03:00
|
|
|
fn apply_sql(&mut self, sql_content: &str) -> StdResult<()> {
|
|
|
|
self.conn.batch_execute(sql_content)
|
|
|
|
}
|
|
|
|
|
2021-02-15 13:47:45 +03:00
|
|
|
fn create_migrations_table(&mut self) -> StdResult<()> {
|
2021-02-15 23:22:13 +03:00
|
|
|
self.conn.batch_execute(
|
|
|
|
r#"CREATE TABLE IF NOT EXISTS migrations (
|
|
|
|
id serial PRIMARY KEY,
|
|
|
|
name text NOT NULL UNIQUE
|
2021-02-15 23:23:35 +03:00
|
|
|
)"#,
|
2021-02-15 23:22:13 +03:00
|
|
|
)
|
2021-02-15 13:47:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn insert_migration_info(&mut self, name: &str) -> StdResult<u64> {
|
2021-02-15 23:23:35 +03:00
|
|
|
self.conn
|
|
|
|
.execute("INSERT INTO migrations (name) VALUES ($1)", &[&name])
|
2021-02-15 13:47:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn delete_migration_info(&mut self, name: &str) -> StdResult<u64> {
|
2021-02-15 23:23:35 +03:00
|
|
|
self.conn
|
|
|
|
.execute("DELETE FROM migrations WHERE name = $1", &[&name])
|
2021-02-15 13:47:45 +03:00
|
|
|
}
|
|
|
|
|
2021-02-18 12:29:09 +03:00
|
|
|
fn applied_migration_names(&mut self) -> StdResult<Vec<String>> {
|
|
|
|
let res = self
|
|
|
|
.conn
|
|
|
|
.query("SELECT name FROM migrations ORDER BY id DESC", &[])
|
|
|
|
.map(|row| row.first().unwrap().clone())
|
|
|
|
.or_else(|e| {
|
|
|
|
if is_migrations_table_not_found(&e) {
|
|
|
|
Ok(Vec::new())
|
|
|
|
} else {
|
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(res.into_iter().collect())
|
|
|
|
}
|
2021-02-15 13:47:45 +03:00
|
|
|
}
|
|
|
|
|
2021-02-15 13:06:09 +03:00
|
|
|
pub fn filter_pending_migrations(
|
|
|
|
migrations: Vec<Migration>,
|
|
|
|
applied_migration_names: &[String],
|
|
|
|
) -> Vec<Migration> {
|
|
|
|
migrations
|
|
|
|
.into_iter()
|
|
|
|
.filter(|m| !applied_migration_names.contains(m.name()))
|
|
|
|
.collect()
|
2021-02-08 23:42:13 +03:00
|
|
|
}
|