aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--examples/tutorial02/polls/mod.rs1
-rw-r--r--src/apps.rs1
-rw-r--r--src/cli/migrate.rs38
-rw-r--r--src/contrib/admin/migrations/m_0001_initial.rs6
-rw-r--r--src/contrib/admin/mod.rs1
-rw-r--r--src/db/backend.rs3
-rw-r--r--src/db/migration/change.rs62
-rw-r--r--src/db/migration/mod.rs21
-rw-r--r--src/db/models/mod.rs15
10 files changed, 138 insertions, 11 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9cf36a9..ed90c9c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
+ },
+}