use chrono::{DateTime, Utc};
use std;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use crate::config::Config;
#[cfg(not(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql")))]
use crate::connection::markers::DatabaseFeatureRequired;
use crate::connection::ConnConfig;
use crate::drivers;
use crate::errors::*;
use crate::migratable::Migratable;
use crate::{DbKind, Direction, DT_FORMAT};
#[derive(Clone, Debug)]
pub struct FileMigration {
pub tag: String,
pub up: Option<PathBuf>,
pub down: Option<PathBuf>,
pub(crate) stamp: Option<DateTime<Utc>>,
}
impl FileMigration {
pub fn with_tag(tag: &str) -> Self {
Self {
tag: tag.to_owned(),
up: None,
down: None,
stamp: None,
}
}
fn check_path(path: &Path) -> Result<()> {
if !path.exists() {
bail_fmt!(
ErrorKind::MigrationNotFound,
"Migration file not found: {:?}",
path
)
}
Ok(())
}
pub fn up<T: AsRef<Path>>(&mut self, up_file: T) -> Result<&mut Self> {
let path = up_file.as_ref();
Self::check_path(path)?;
self.up = Some(path.to_owned());
Ok(self)
}
pub fn down<T: AsRef<Path>>(&mut self, down_file: T) -> Result<&mut Self> {
let path = down_file.as_ref();
Self::check_path(path)?;
self.down = Some(path.to_owned());
Ok(self)
}
pub fn boxed(&self) -> Box<dyn Migratable> {
Box::new(self.clone())
}
}
impl Migratable for FileMigration {
fn apply_up(
&self,
db_kind: DbKind,
config: &Config,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
if let Some(ref up) = self.up {
match db_kind {
DbKind::Sqlite => {
let db_path = config.database_path()?;
drivers::sqlite::run_migration(&db_path, up)?;
}
DbKind::Postgres => {
let conn_str = config.connect_string()?;
drivers::pg::run_migration(config.ssl_cert_file().as_deref(), &conn_str, up)?;
}
DbKind::MySql => {
let conn_str = config.connect_string()?;
drivers::mysql::run_migration(&conn_str, up)?;
}
}
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn apply_down(
&self,
db_kind: DbKind,
config: &Config,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
if let Some(ref down) = self.down {
match db_kind {
DbKind::Sqlite => {
let db_path = config.database_path()?;
drivers::sqlite::run_migration(&db_path, down)?;
}
DbKind::Postgres => {
let conn_str = config.connect_string()?;
drivers::pg::run_migration(config.ssl_cert_file().as_deref(), &conn_str, down)?;
}
DbKind::MySql => {
let conn_str = config.connect_string()?;
drivers::mysql::run_migration(&conn_str, down)?;
}
}
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn tag(&self) -> String {
match self.stamp.as_ref() {
Some(dt) => {
let dt_string = dt.format(DT_FORMAT).to_string();
format!("{}_{}", dt_string, self.tag)
}
None => self.tag.to_owned(),
}
}
fn description(&self, direction: &Direction) -> String {
match *direction {
Direction::Up => self
.up
.as_ref()
.map(|p| format!("{:?}", p))
.unwrap_or_else(|| self.tag()),
Direction::Down => self
.down
.as_ref()
.map(|p| format!("{:?}", p))
.unwrap_or_else(|| self.tag()),
}
}
}
#[derive(Clone, Debug)]
pub struct EmbeddedMigration {
pub tag: String,
pub up: Option<Cow<'static, str>>,
pub down: Option<Cow<'static, str>>,
}
impl EmbeddedMigration {
#[cfg(not(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql")))]
pub fn with_tag(_tag: &str) -> DatabaseFeatureRequired {
unimplemented!();
}
#[cfg(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql"))]
pub fn with_tag(tag: &str) -> Self {
Self {
tag: tag.to_owned(),
up: None,
down: None,
}
}
pub fn up<T: Into<Cow<'static, str>>>(&mut self, stmt: T) -> &mut Self {
self.up = Some(stmt.into());
self
}
pub fn down<T: Into<Cow<'static, str>>>(&mut self, stmt: T) -> &mut Self {
self.down = Some(stmt.into());
self
}
pub fn boxed(&self) -> Box<dyn Migratable> {
Box::new(self.clone())
}
}
impl Migratable for EmbeddedMigration {
fn apply_up(
&self,
_db_kind: DbKind,
_config: &Config,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
if let Some(ref _up) = self.up {
#[cfg(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql"))]
match _db_kind {
DbKind::Sqlite => {
let db_path = _config.database_path()?;
drivers::sqlite::run_migration_str(&db_path, _up.as_ref())?;
}
DbKind::Postgres => {
let conn_str = _config.connect_string()?;
drivers::pg::run_migration_str(
_config.ssl_cert_file().as_deref(),
&conn_str,
_up.as_ref(),
)?;
}
DbKind::MySql => {
let conn_str = _config.connect_string()?;
drivers::mysql::run_migration_str(&conn_str, _up.as_ref())?;
}
}
#[cfg(not(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql")))]
panic!("** Migrant ERROR: Database specific feature required to run embedded-file migration **");
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn apply_down(
&self,
db_kind: DbKind,
config: &Config,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
if let Some(ref down) = self.down {
match db_kind {
DbKind::Sqlite => {
let db_path = config.database_path()?;
drivers::sqlite::run_migration_str(&db_path, down.as_ref())?;
}
DbKind::Postgres => {
let conn_str = config.connect_string()?;
drivers::pg::run_migration_str(
config.ssl_cert_file().as_deref(),
&conn_str,
down.as_ref(),
)?;
}
DbKind::MySql => {
let conn_str = config.connect_string()?;
drivers::mysql::run_migration_str(&conn_str, down.as_ref())?;
}
}
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn tag(&self) -> String {
self.tag.to_owned()
}
fn description(&self, _: &Direction) -> String {
self.tag()
}
}
pub fn noop(_: ConnConfig) -> std::result::Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[derive(Clone, Debug)]
pub struct FnMigration<T, U> {
pub tag: String,
pub up: Option<T>,
pub down: Option<U>,
}
impl<T, U> FnMigration<T, U>
where
T: 'static + Clone + Fn(ConnConfig) -> std::result::Result<(), Box<dyn std::error::Error>>,
U: 'static + Clone + Fn(ConnConfig) -> std::result::Result<(), Box<dyn std::error::Error>>,
{
#[cfg(not(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql")))]
pub fn with_tag(_tag: &str) -> DatabaseFeatureRequired {
unimplemented!();
}
#[cfg(any(feature = "d-postgres", feature = "d-sqlite", feature = "d-mysql"))]
pub fn with_tag(tag: &str) -> Self {
Self {
tag: tag.to_owned(),
up: None,
down: None,
}
}
pub fn up(&mut self, f_up: T) -> &mut Self {
self.up = Some(f_up);
self
}
pub fn down(&mut self, f_down: U) -> &mut Self {
self.down = Some(f_down);
self
}
pub fn boxed(&self) -> Box<dyn Migratable> {
Box::new(self.clone())
}
}
impl<T, U> Migratable for FnMigration<T, U>
where
T: 'static + Clone + Fn(ConnConfig) -> std::result::Result<(), Box<dyn std::error::Error>>,
U: 'static + Clone + Fn(ConnConfig) -> std::result::Result<(), Box<dyn std::error::Error>>,
{
fn apply_up(
&self,
_: DbKind,
config: &Config,
) -> std::result::Result<(), Box<dyn (::std::error::Error)>> {
if let Some(ref up) = self.up {
up(ConnConfig::new(config))?;
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn apply_down(
&self,
_: DbKind,
config: &Config,
) -> std::result::Result<(), Box<dyn (::std::error::Error)>> {
if let Some(ref down) = self.down {
down(ConnConfig::new(config))?;
} else {
print_flush!("(empty) ...");
}
Ok(())
}
fn tag(&self) -> String {
self.tag.to_owned()
}
fn description(&self, _: &Direction) -> String {
self.tag()
}
}