pub mod validate;
use crate::backends::TableInfo;
use crate::builder::models::QueryType;
use crate::error::MigrationError;
use crate::{Database, GeekConnection, Query, Table, Values};
use self::validate::Validator;
#[derive(Debug)]
pub enum MigrationState {
Initialized,
UpToDate,
OutOfDate(String),
}
pub(crate) type DatabaseTables = Vec<(String, Vec<TableInfo>)>;
pub trait Migration
where
Self: Sync + Send,
{
fn version() -> &'static str
where
Self: Sized;
fn create_query() -> &'static str
where
Self: Sized;
fn upgrade_query() -> &'static str
where
Self: Sized,
{
""
}
fn rollback_query() -> &'static str
where
Self: Sized,
{
""
}
fn previous() -> Option<Box<dyn Migration>>
where
Self: Sized,
{
None
}
fn database(&self) -> &Database;
#[allow(async_fn_in_trait, unused_variables)]
async fn validate_database<'a, C>(
&self,
connection: &'a C,
database: &Database,
) -> Result<MigrationState, crate::Error>
where
Self: Sized,
C: GeekConnection<Connection = C> + 'a,
{
let database_tables = C::table_names(connection).await?;
if database_tables.is_empty() {
return Ok(MigrationState::Initialized);
}
let mut database_table_columns: DatabaseTables = Vec::new();
for table in database_tables {
let dbcolumns = C::pragma_info(connection, table.as_str()).await?;
database_table_columns.push((table, dbcolumns));
}
let mut migrations: Vec<Box<dyn Migration>> = Vec::new();
#[cfg(feature = "log")]
{
log::debug!("Validating database schema");
}
let state = Self::validate(&mut migrations, database, &database_table_columns)?;
for migration in migrations {
#[cfg(feature = "log")]
{
let v = Self::version();
log::info!("Upgrading database to version {}", v);
}
Self::upgrade(connection).await?;
}
if matches!(state, MigrationState::OutOfDate(_)) {
#[cfg(feature = "log")]
{
log::info!("Upgrading database to version {}", Self::version());
}
Self::upgrade(connection).await?;
}
Ok(MigrationState::UpToDate)
}
#[allow(unused_variables)]
fn validate(
migrations: &mut Vec<Box<dyn Migration>>,
migration_database: &Database,
live_database: &DatabaseTables,
) -> Result<MigrationState, crate::Error>
where
Self: Sized,
{
let mut validator = Validator {
errors: Vec::new(),
quick: true,
};
let result =
validate::validate_database(live_database, migration_database, &mut validator)?;
match result {
MigrationState::OutOfDate(reason) => {
#[cfg(feature = "log")]
{
log::info!("Database is out of date: {}", reason);
}
if let Some(prev) = Self::previous() {
migrations.push(prev);
}
Ok(MigrationState::OutOfDate(reason))
}
_ => Ok(MigrationState::UpToDate),
}
}
#[allow(async_fn_in_trait)]
async fn create<'a, C>(connection: &'a C) -> Result<(), crate::Error>
where
Self: Sized,
C: GeekConnection<Connection = C> + 'a,
{
let query = Self::create_query().to_string();
C::batch(
connection,
Query::new(
QueryType::Create,
query,
Values::new(),
Values::new(),
Vec::new(),
Table::default(),
),
)
.await
}
#[allow(async_fn_in_trait)]
async fn upgrade<'a, C>(connection: &'a C) -> Result<(), crate::Error>
where
Self: Sized,
C: GeekConnection<Connection = C> + 'a,
{
let query = Self::upgrade_query().to_string();
if query.is_empty() {
#[cfg(feature = "log")]
{
log::warn!("No upgrade query found");
}
return Err(crate::Error::MigrationError(MigrationError::UpgradeError(
"No upgrade is avalible".to_string(),
)));
}
#[cfg(feature = "log")]
{
log::debug!("Executing upgrade query: {}", query);
}
C::batch(
connection,
Query::new(
QueryType::Update,
query,
Values::new(),
Values::new(),
Vec::new(),
Table::default(),
),
)
.await
}
#[allow(async_fn_in_trait)]
async fn rollback<'a, C>(connection: &'a C) -> Result<(), crate::Error>
where
Self: Sized,
C: GeekConnection<Connection = C> + 'a,
{
let query = Self::rollback_query().to_string();
if query.is_empty() {
#[cfg(feature = "log")]
{
log::debug!("No rollback query found");
}
return Ok(());
}
#[cfg(feature = "log")]
{
log::debug!("Executing rollback query: {}", query);
}
C::execute(
connection,
Query::new(
QueryType::Update,
query,
Values::new(),
Values::new(),
Vec::new(),
Table::default(),
),
)
.await
}
#[allow(async_fn_in_trait, unused_variables)]
async fn migrate<'a, C>(connection: &'a C) -> Result<(), crate::Error>
where
Self: Sized,
C: GeekConnection<Connection = C> + 'a,
{
Ok(())
}
}