From 58d2f63f4577bc701b6bd655064cefebb65118b4 Mon Sep 17 00:00:00 2001 From: Melody Horn / boringcactus Date: Wed, 16 Jun 2021 14:22:50 -0600 Subject: actually run migrations --- src/db/backend.rs | 3 +++ src/db/migration/change.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++ src/db/migration/mod.rs | 21 ++++++++++++++-- src/db/models/mod.rs | 15 +++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/db/migration/change.rs (limited to 'src/db') 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; } @@ -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::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(&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::()).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(&self, app_name: &str, connection: &C::Connection) { + // TODO prevent double-applying + connection.transaction::<_, DieselError, _>(|| { + for change in self.changes { + change.apply::(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, + }, + + DateTimeField { + name: &'static str, + }, + + IntField { + name: &'static str, + }, +} -- cgit v1.2.3