1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
use crate::{checksum, BoxFuture, ConnectorError, ConnectorResult, Namespaces};
/// A timestamp.
pub type Timestamp = chrono::DateTime<chrono::Utc>;
/// Management of imperative migrations state in the database.
pub trait MigrationPersistence: Send + Sync {
/// Initialize the migration persistence without checking the database first.
fn baseline_initialize(&mut self) -> BoxFuture<'_, ConnectorResult<()>>;
/// This method is responsible for checking whether the migrations
/// persistence is initialized.
///
/// If the migration persistence is not present in the target database,
/// check whether the database schema is empty. If it is, initialize the
/// migration persistence. If not, return a DatabaseSchemaNotEmpty error.
fn initialize(&mut self, namespaces: Option<Namespaces>) -> BoxFuture<'_, ConnectorResult<()>>;
/// Implementation in the connector for the core's MarkMigrationApplied
/// command. See the docs there. Note that the started_at and finished_at
/// for the migration should be the same.
///
/// Connectors should implement mark_migration_applied_impl to avoid doing
/// the checksuming themselves.
fn mark_migration_applied<'a>(
&'a mut self,
migration_name: &'a str,
script: &'a str,
) -> BoxFuture<'a, ConnectorResult<String>> {
Box::pin(async move {
self.mark_migration_applied_impl(migration_name, &checksum::render_checksum(script))
.await
})
}
/// Implementation in the connector for the core's MarkMigrationApplied
/// command. See the docs there. Note that the started_at and finished_at
/// for the migration should be the same.
fn mark_migration_applied_impl<'a>(
&'a mut self,
migration_name: &'a str,
checksum: &'a str,
) -> BoxFuture<'a, ConnectorResult<String>>;
/// Mark the failed instances of the migration in the persistence as rolled
/// back, so they will be ignored by the engine in the future.
fn mark_migration_rolled_back_by_id<'a>(&'a mut self, migration_id: &'a str) -> BoxFuture<'a, ConnectorResult<()>>;
/// Record that a migration is about to be applied. Returns the unique
/// identifier for the migration.
///
/// This is a default method that computes the checksum. Implementors should
/// implement record_migration_started_impl.
fn record_migration_started<'a>(
&'a mut self,
migration_name: &'a str,
script: &'a str,
) -> BoxFuture<'a, ConnectorResult<String>> {
Box::pin(async move {
self.record_migration_started_impl(migration_name, &checksum::render_checksum(script))
.await
})
}
/// Record that a migration is about to be applied. Returns the unique
/// identifier for the migration.
///
/// This is an implementation detail, consumers should use
/// `record_migration_started()` instead.
fn record_migration_started_impl<'a>(
&'a mut self,
migration_name: &'a str,
checksum: &'a str,
) -> BoxFuture<'a, ConnectorResult<String>>;
/// Increase the applied_steps_count counter.
fn record_successful_step<'a>(&'a mut self, id: &'a str) -> BoxFuture<'a, ConnectorResult<()>>;
/// Report logs for a failed migration step. We assume the next steps in the
/// migration will not be applied, and the error reported.
fn record_failed_step<'a>(&'a mut self, id: &'a str, logs: &'a str) -> BoxFuture<'a, ConnectorResult<()>>;
/// Record that the migration completed *successfully*. This means
/// populating the `finished_at` field in the migration record.
fn record_migration_finished<'a>(&'a mut self, id: &'a str) -> BoxFuture<'a, ConnectorResult<()>>;
/// List all applied migrations, ordered by `started_at`. This should fail
/// with a PersistenceNotInitializedError when the migration persistence is
/// not initialized.
fn list_migrations(
&mut self,
) -> BoxFuture<'_, ConnectorResult<Result<Vec<MigrationRecord>, PersistenceNotInitializedError>>>;
}
/// Error returned when the persistence is not initialized.
#[derive(Debug)]
pub struct PersistenceNotInitializedError;
impl PersistenceNotInitializedError {
/// Explicit conversion to a ConnectorError.
pub fn into_connector_error(self) -> ConnectorError {
ConnectorError::from_msg("Invariant violation: migration persistence is not initialized.".into())
}
}
/// An applied migration, as returned by list_migrations.
#[derive(Debug)]
pub struct MigrationRecord {
/// A unique, randomly generated identifier.
pub id: String,
/// The SHA-256 checksum of the migration script, to detect if it was
/// edited. It covers only the content of the script file, it does not
/// include timestamp or migration name information.
pub checksum: String,
/// The timestamp at which the migration completed *successfully*.
pub finished_at: Option<Timestamp>,
/// The name of the migration, i.e. the name of migration directory
/// containing the migration script.
pub migration_name: String,
/// The human-readable log of actions performed by the engine, up to and
/// including the point where the migration failed, with the relevant error.
pub logs: Option<String>,
/// If the migration was rolled back, and when.
pub rolled_back_at: Option<Timestamp>,
/// The time the migration started being applied.
pub started_at: Timestamp,
/// The number of migration steps that were successfully applied.
pub applied_steps_count: u32,
}