diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/tutorial02/polls/mod.rs | 1 | ||||
-rw-r--r-- | src/apps.rs | 1 | ||||
-rw-r--r-- | src/cli/migrate.rs | 38 | ||||
-rw-r--r-- | src/contrib/admin/migrations/m_0001_initial.rs | 6 | ||||
-rw-r--r-- | src/contrib/admin/mod.rs | 1 | ||||
-rw-r--r-- | src/db/backend.rs | 3 | ||||
-rw-r--r-- | src/db/migration/change.rs | 62 | ||||
-rw-r--r-- | src/db/migration/mod.rs | 21 | ||||
-rw-r--r-- | src/db/models/mod.rs | 15 |
10 files changed, 138 insertions, 11 deletions
@@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +barrel = { version = "0.6.5", features = ["sqlite3"] } diesel = { version = "1.4.4", features = ["sqlite"] } hyper = { version = "0.14", features = ["full"] } structopt = "0.3.21" diff --git a/examples/tutorial02/polls/mod.rs b/examples/tutorial02/polls/mod.rs index 4b5de71..0b039d9 100644 --- a/examples/tutorial02/polls/mod.rs +++ b/examples/tutorial02/polls/mod.rs @@ -8,5 +8,6 @@ pub mod views; pub use urls::urls; pub const APP: AppConfig = AppConfig { + name: module_path!(), migrations: migrations::migrations, }; diff --git a/src/apps.rs b/src/apps.rs index 785ce8c..f72a883 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -1,5 +1,6 @@ use crate::db::migration::Migration; pub struct AppConfig { + pub name: &'static str, pub migrations: &'static [Migration], } diff --git a/src/cli/migrate.rs b/src/cli/migrate.rs index 4b5123a..a6efbb5 100644 --- a/src/cli/migrate.rs +++ b/src/cli/migrate.rs @@ -1,19 +1,47 @@ use structopt::StructOpt; -use crate::{Settings, UrlMap, db::backend::{Connectable, Connection}}; +use crate::{Settings, UrlMap}; +use crate::db::backend::Connectable; +use crate::db::migration::{DatabaseChange, CreateModelOption}; +use crate::db::models::Field; #[derive(StructOpt)] /// Perform database migrations pub struct Migrate { } +const CREATE_MIGRATION_TABLE: DatabaseChange = DatabaseChange::CreateModel { + name: "Migration", + fields: &[ + Field::CharField { + name: "app_name", + max_length: None, + }, + Field::IntField { + name: "migration_id", + }, + Field::CharField { + name: "migration_name", + max_length: None, + }, + Field::DateTimeField { + name: "applied_at", + } + ], + options: &[CreateModelOption::IfNotExist], +}; + impl Migrate { - pub fn execute(self, _urls: UrlMap, settings: Settings<impl Connectable>) { + pub fn execute<C: Connectable>(self, _urls: UrlMap, settings: Settings<C>) { let database = settings.database; let connection = database.connect().unwrap(); - let migration_table_vibe_check = connection.transaction::<(), diesel::result::Error, _>(|| { - todo!() - }); + CREATE_MIGRATION_TABLE.apply::<C>("tosin_meta", &connection); + + for app in settings.installed_apps { + for migration in app.migrations { + migration.apply::<C>(app.name, &connection); + } + } } } diff --git a/src/contrib/admin/migrations/m_0001_initial.rs b/src/contrib/admin/migrations/m_0001_initial.rs index 6fed631..ebfdc47 100644 --- a/src/contrib/admin/migrations/m_0001_initial.rs +++ b/src/contrib/admin/migrations/m_0001_initial.rs @@ -1,10 +1,8 @@ -use crate::db::migration::{Migration, DatabaseChange}; +use crate::db::migration::Migration; pub const MIGRATION: Migration = Migration { id: 1, name: "initial", prereqs: &[], - changes: &[ - DatabaseChange::CreateModel, - ], + changes: &[], }; diff --git a/src/contrib/admin/mod.rs b/src/contrib/admin/mod.rs index 5098a4a..f92e709 100644 --- a/src/contrib/admin/mod.rs +++ b/src/contrib/admin/mod.rs @@ -4,5 +4,6 @@ mod migrations; pub mod site; pub const APP: AppConfig = AppConfig { + name: module_path!(), migrations: migrations::migrations, }; diff --git a/src/db/backend.rs b/src/db/backend.rs index b75d5ca..5f35dd9 100644 --- a/src/db/backend.rs +++ b/src/db/backend.rs @@ -1,7 +1,9 @@ +use barrel::backend::SqlGenerator; pub use diesel::connection::Connection; pub trait Connectable { type Connection: Connection; + type SqlGenerator: SqlGenerator; fn connect(&self) -> diesel::ConnectionResult<Self::Connection>; } @@ -11,6 +13,7 @@ pub struct Sqlite { impl Connectable for Sqlite { type Connection = diesel::sqlite::SqliteConnection; + type SqlGenerator = barrel::backend::Sqlite; fn connect(&self) -> diesel::ConnectionResult<Self::Connection> { Self::Connection::establish(self.db_file) diff --git a/src/db/migration/change.rs b/src/db/migration/change.rs new file mode 100644 index 0000000..b5d0dd5 --- /dev/null +++ b/src/db/migration/change.rs @@ -0,0 +1,62 @@ +use diesel::Connection; + +use crate::db::backend::Connectable; +use crate::db::models::Field; + +pub enum DatabaseChange { + CreateModel { + name: &'static str, + fields: &'static [Field], + options: &'static [CreateModelOption] + }, +} + +impl DatabaseChange { + pub fn apply<C: Connectable>(&self, app_name: &str, connection: &C::Connection) { + use barrel::{Migration, Table, types}; + + match self { + DatabaseChange::CreateModel { name, fields, options } => { + let mut m = Migration::new(); + + let columns: Vec<(&'static str, _)> = fields.iter().map(|field| match field { + Field::CharField { name, max_length } => { + let name = *name; + let _type = match max_length { + None => types::text(), + Some(max_length) => types::varchar(*max_length), + }; + (name, _type) + } + Field::DateTimeField { name } => { + (*name, types::text()) // TODO do smart things on non-sqlite + } + Field::IntField { name } => { + (*name, types::integer()) + } + }).collect(); + + let callback = move |t: &mut Table| { + for (name, _type) in &columns { + t.add_column(*name, _type.clone()); + } + }; + + let table_name = format!("{}-{}", app_name, name); + + if options.contains(&CreateModelOption::IfNotExist) { + m.create_table_if_not_exists(table_name, callback); + } else { + m.create_table(table_name, callback); + } + + connection.execute(&m.make::<C::SqlGenerator>()).unwrap(); + } + } + } +} + +#[derive(PartialEq)] +pub enum CreateModelOption { + IfNotExist, +} diff --git a/src/db/migration/mod.rs b/src/db/migration/mod.rs index d53f46b..d511f88 100644 --- a/src/db/migration/mod.rs +++ b/src/db/migration/mod.rs @@ -1,5 +1,13 @@ pub use tosin_macros::gather_migrations as gather; +use diesel::{Connection, result::Error as DieselError}; + +use crate::db::backend::Connectable; + +mod change; + +pub use change::*; + pub struct Migration { pub id: usize, pub name: &'static str, @@ -7,6 +15,15 @@ pub struct Migration { pub changes: &'static [DatabaseChange], } -pub enum DatabaseChange { - CreateModel, +impl Migration { + pub fn apply<C: Connectable>(&self, app_name: &str, connection: &C::Connection) { + // TODO prevent double-applying + connection.transaction::<_, DieselError, _>(|| { + for change in self.changes { + change.apply::<C>(app_name, connection); + } + + Ok(()) + }).unwrap(); + } } diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 5e59949..492df92 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -1,3 +1,18 @@ pub use tosin_macros::Model; pub type Id = usize; + +pub enum Field { + CharField { + name: &'static str, + max_length: Option<usize>, + }, + + DateTimeField { + name: &'static str, + }, + + IntField { + name: &'static str, + }, +} |