use async_trait::async_trait;
use mongodb::bson::{doc, Bson, Document};
use mongodb::{options, Collection, Database};
use crate::error::{Result, WitherError};
use crate::model::Model;
#[async_trait]
pub trait Migrating: Model {
fn migrations() -> Vec<Box<dyn Migration>>;
async fn migrate(db: &Database) -> Result<()> {
let coll = Self::collection(db);
let ns = coll.namespace();
let migrations = Self::migrations();
log::info!("Starting migrations for '{}'.", ns);
for migration in migrations {
migration.execute(&coll).await?;
}
log::info!("Finished migrations for '{}'.", ns);
Ok(())
}
}
#[cfg_attr(feature = "docinclude", doc(include = "../docs/migrations-overview.md"))]
#[async_trait]
pub trait Migration: Send + Sync {
async fn execute<'c>(&self, coll: &'c Collection) -> Result<()>;
}
pub struct IntervalMigration {
pub name: String,
pub threshold: chrono::DateTime<chrono::Utc>,
pub filter: Document,
pub set: Option<Document>,
pub unset: Option<Document>,
}
#[async_trait]
impl Migration for IntervalMigration {
async fn execute<'c>(&self, coll: &'c Collection) -> Result<()> {
let ns = coll.namespace();
log::info!("Executing migration '{}' against '{}'.", &self.name, ns);
if chrono::Utc::now() > self.threshold {
log::info!("Successfully executed migration '{}' against '{}'. No-op.", &self.name, ns);
return Ok(());
};
let mut update = doc! {};
if self.set.clone().is_none() && self.unset.clone().is_none() {
return Err(WitherError::MigrationSetOrUnsetRequired);
};
if let Some(set) = self.set.clone() {
update.insert("$set", Bson::from(set));
}
if let Some(unset) = self.unset.clone() {
update.insert("$unset", Bson::from(unset));
}
let options = options::UpdateOptions::builder()
.upsert(Some(false))
.write_concern(Some(
options::WriteConcern::builder()
.w(Some(options::Acknowledgment::Majority))
.journal(Some(true))
.build(),
))
.build();
let res = coll.update_many(self.filter.clone(), update, Some(options)).await?;
log::info!(
"Successfully executed migration '{}' against '{}'. {} matched. {} modified.",
&self.name,
ns,
res.matched_count,
res.modified_count
);
Ok(())
}
}