My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/development/libs/barrel/src/migration.rs

221 lines
7.6 KiB

//! Core migration creation handler
//!
//! A migration can be done for a specific schema which contains
//! multiple additions or removables from a database or table.
//!
//! At the end of crafting a migration you can use `Migration::exec` to
//! get the raw SQL string for a database backend or `Migration::revert`
//! to try to auto-infer the migration rollback. In cases where that
//! can't be done the `Result<String, RevertError>` will not unwrap.
//!
//! You can also use `Migration::exec` with your SQL connection for convenience
//! if you're a library developer.
use crate::table::{Table, TableMeta};
use crate::DatabaseChange;
use crate::backend::{SqlGenerator, SqlVariant};
use crate::connectors::SqlRunner;
use std::rc::Rc;
/// Represents a schema migration on a database
pub struct Migration {
#[doc(hidden)]
pub schema: Option<String>,
#[doc(hidden)]
pub changes: Vec<DatabaseChange>,
}
impl Migration {
pub fn new() -> Migration {
Migration {
schema: None,
changes: Vec::new(),
}
}
/// Specify a database schema name for this migration
pub fn schema<S: Into<String>>(self, schema: S) -> Migration {
Self {
schema: Some(schema.into()),
..self
}
}
/// Creates the SQL for this migration for a specific backend
///
/// This function copies state and does not touch the original
/// migration layout. This allows you to call `revert` later on
/// in the process to auto-infer the down-behaviour
pub fn make<T: SqlGenerator>(&self) -> String {
use DatabaseChange::*;
/* What happens in make, stays in make (sort of) */
let mut changes = self.changes.clone();
let schema = self.schema.as_ref().map(|s| s.as_str());
changes.iter_mut().fold(String::new(), |mut sql, change| {
match change {
&mut CreateTable(ref mut t, ref mut cb)
| &mut CreateTableIfNotExists(ref mut t, ref mut cb) => {
cb(t); // Run the user code
let (cols, indices) = t.make::<T>(false, schema);
let name = t.meta.name().clone();
sql.push_str(&match change {
CreateTable(_, _) => T::create_table(&name, schema),
CreateTableIfNotExists(_, _) => {
T::create_table_if_not_exists(&name, schema)
}
_ => unreachable!(),
});
sql.push_str(" (");
let l = cols.len();
for (i, slice) in cols.iter().enumerate() {
sql.push_str(slice);
if i < l - 1 {
sql.push_str(", ");
}
}
sql.push_str(")");
// Add additional index columns
if indices.len() > 0 {
sql.push_str(";");
sql.push_str(&indices.join(";"));
}
}
&mut DropTable(ref name) => sql.push_str(&T::drop_table(name, schema)),
&mut DropTableIfExists(ref name) => {
sql.push_str(&T::drop_table_if_exists(name, schema))
}
&mut RenameTable(ref old, ref new) => {
sql.push_str(&T::rename_table(old, new, schema))
}
&mut ChangeTable(ref mut t, ref mut cb) => {
cb(t);
let (cols, indices) = t.make::<T>(true, schema);
sql.push_str(&T::alter_table(&t.meta.name(), schema));
sql.push_str(" ");
let l = cols.len();
for (i, slice) in cols.iter().enumerate() {
sql.push_str(slice);
if i < l - 1 {
sql.push_str(", ");
}
}
// Add additional index columns
if indices.len() > 0 {
sql.push_str(";");
sql.push_str(&indices.join(";"));
}
}
}
sql.push_str(";");
sql
})
}
/// The same as `make` but making a run-time check for sql variant
///
/// The `SqlVariant` type is populated based on the backends
/// that are being selected at compile-time.
///
/// This function panics if the provided variant is empty!
pub fn make_from(&self, variant: SqlVariant) -> String {
variant.run_for(self)
}
/// Inject a line of custom SQL into the top-level migration scope
///
/// This is a bypass to the barrel typesystem, in case there is
/// something your database supports that barrel doesn't, or if
/// there is an issue with the way that barrel represents types.
/// It does however mean that the SQL provided needs to be
/// specific for one database, meaning that future migrations
/// might become cumbersome.
pub fn inject_custom<S: Into<String>>(&mut self, sql: S) {
self.changes.push(DatabaseChange::CustomLine(sql.into()));
}
/// Automatically infer the `down` step of this migration
///
/// Will thrown an error if behaviour is ambiguous or not
/// possible to infer (e.g. revert a `drop_table`)
pub fn revert<T: SqlGenerator>(&self) -> String {
unimplemented!()
}
/// Pass a reference to a migration toolkit runner which will
/// automatically generate and execute
pub fn execute<S: SqlGenerator, T: SqlRunner>(&self, runner: &mut T) {
runner.execute(self.make::<S>());
}
/// Create a new table with a specific name
pub fn create_table<S: Into<String>, F: 'static>(&mut self, name: S, cb: F) -> &mut TableMeta
where
F: Fn(&mut Table),
{
self.changes
.push(DatabaseChange::CreateTable(Table::new(name), Rc::new(cb)));
match self.changes.last_mut().unwrap() {
&mut DatabaseChange::CreateTable(ref mut t, _) => &mut t.meta,
_ => unreachable!(),
}
}
/// Create a new table *only* if it doesn't exist yet
pub fn create_table_if_not_exists<S: Into<String>, F: 'static>(
&mut self,
name: S,
cb: F,
) -> &mut TableMeta
where
F: Fn(&mut Table),
{
self.changes.push(DatabaseChange::CreateTableIfNotExists(
Table::new(name),
Rc::new(cb),
));
match self.changes.last_mut().unwrap() {
&mut DatabaseChange::CreateTableIfNotExists(ref mut t, _) => &mut t.meta,
_ => unreachable!(),
}
}
/// Change fields on an existing table
pub fn change_table<S: Into<String>, F: 'static>(&mut self, name: S, cb: F)
where
F: Fn(&mut Table),
{
let t = Table::new(name);
let c = DatabaseChange::ChangeTable(t, Rc::new(cb));
self.changes.push(c);
}
/// Rename a table
pub fn rename_table<S: Into<String>>(&mut self, old: S, new: S) {
self.changes
.push(DatabaseChange::RenameTable(old.into(), new.into()));
}
/// Drop an existing table
pub fn drop_table<S: Into<String>>(&mut self, name: S) {
self.changes.push(DatabaseChange::DropTable(name.into()));
}
/// Only drop a table if it exists
pub fn drop_table_if_exists<S: Into<String>>(&mut self, name: S) {
self.changes
.push(DatabaseChange::DropTableIfExists(name.into()));
}
}