#![cfg_attr(feature="docinclude", doc(include="../docs/migrations-overview.md"))]
use std::error::Error;
use chrono;
use mongodb::{
Bson, Document,
db::{
Database,
ThreadedDatabase,
},
coll::{
Collection,
options::UpdateOptions,
},
common::WriteConcern,
error::{
Error::{DefaultError, WriteError},
Result,
},
};
use crate::model::Model;
pub trait Migrating<'m>: Model<'m> {
fn migrations() -> Vec<Box<dyn Migration>>;
fn migrate(db: Database) -> Result<()> {
let coll = db.collection(Self::COLLECTION_NAME);
let migrations = Self::migrations();
info!("Starting migrations for '{}'.", coll.namespace);
for migration in migrations {
migration.execute(&coll)?;
}
info!("Finished migrations for '{}'.", coll.namespace);
Ok(())
}
}
pub trait Migration {
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>,
}
impl Migration for IntervalMigration {
fn execute<'c>(&self, coll: &'c Collection) -> Result<()> {
info!("Executing migration '{}' against '{}'.", &self.name, coll.namespace);
if chrono::Utc::now() > self.threshold {
info!("Successfully executed migration '{}' against '{}'. No-op.", &self.name, coll.namespace);
return Ok(());
};
let mut update = doc!{};
if self.set.clone().is_none() && self.unset.clone().is_none() {
return Err(DefaultError(String::from("One of '$set' or '$unset' must be specified.")));
};
if let Some(set) = self.set.clone() {
update.insert_bson(String::from("$set"), Bson::from(set));
}
if let Some(unset) = self.unset.clone() {
update.insert_bson(String::from("$unset"), Bson::from(unset));
}
let options = UpdateOptions{upsert: Some(false), write_concern: Some(WriteConcern{w: 1, w_timeout: 0, j: true, fsync: false})};
let res = coll.update_many(self.filter.clone(), update, Some(options))?;
if let Some(err) = res.write_exception {
error!("Error executing migration: {:?}", err.description());
return Err(WriteError(err));
}
info!("Successfully executed migration '{}' against '{}'. {} matched. {} modified.", &self.name, coll.namespace, res.matched_count, res.modified_count);
Ok(())
}
}