diff options
Diffstat (limited to 'development/libs/barrel/src/integrations/diesel.rs')
-rw-r--r-- | development/libs/barrel/src/integrations/diesel.rs | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/development/libs/barrel/src/integrations/diesel.rs b/development/libs/barrel/src/integrations/diesel.rs new file mode 100644 index 000000000000..f376c211fe7b --- /dev/null +++ b/development/libs/barrel/src/integrations/diesel.rs @@ -0,0 +1,197 @@ +//! + +// This integration relies on _knowing_ which backend is being used at compile-time +// This is a poor woman's XOR - if you know how to make it more pretty, PRs welcome <3 +#[cfg(any( + all(feature = "pg", feature = "mysql"), + all(feature = "pg", feature = "sqlite3"), + all(feature = "mysql", feature = "sqlite3") +))] +compile_error!("`barrel` can only integrate with `diesel` if you select one (1) backend!"); + +use diesel_rs::connection::SimpleConnection; +use diesel_rs::migration::{Migration, RunMigrationsError}; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Represents a migration run inside Diesel +/// +/// 1. Path +/// 2. Version +/// 3. Up +/// 4. Down +pub struct BarrelMigration(PathBuf, String, String, String); + +impl Migration for BarrelMigration { + fn file_path(&self) -> Option<&Path> { + Some(self.0.as_path()) + } + + fn version(&self) -> &str { + &self.1 + } + + fn run(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> { + conn.batch_execute(&self.2)?; + Ok(()) + } + + fn revert(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> { + conn.batch_execute(&self.3)?; + Ok(()) + } +} + +/// Generate migration files using the barrel schema builder +pub fn generate_initial(path: &PathBuf) { + generate_initial_with_content( + path, + &"fn up(migr: &mut Migration) {} \n\n".to_string(), + &"fn down(migr: &mut Migration) {} \n".to_string(), + ) +} + +/// Generate migration files using the barrel schema builder with initial content +pub fn generate_initial_with_content(path: &PathBuf, up_content: &String, down_content: &String) { + let migr_path = path.join("mod.rs"); + println!("Creating {}", migr_path.display()); + + let mut barrel_migr = fs::File::create(migr_path).unwrap(); + barrel_migr.write(b"/// Handle up migrations \n").unwrap(); + barrel_migr.write(up_content.as_bytes()).unwrap(); + + barrel_migr.write(b"/// Handle down migrations \n").unwrap(); + barrel_migr.write(down_content.as_bytes()).unwrap(); +} + +/// Generate a Migration from the provided path +pub fn migration_from(path: &Path) -> Option<Box<Migration>> { + match path.join("mod.rs").exists() { + true => Some(run_barrel_migration_wrapper(&path.join("mod.rs"))), + false => None, + } +} + +fn version_from_path(path: &Path) -> Result<String, ()> { + path.parent() + .unwrap_or_else(|| { + panic!( + "Migration doesn't appear to be in a directory: `{:?}`", + path + ) + }) + .file_name() + .unwrap_or_else(|| panic!("Can't get file name from path `{:?}`", path)) + .to_string_lossy() + .split('_') + .nth(0) + .map(|s| Ok(s.replace('-', ""))) + .unwrap_or_else(|| Err(())) +} + +fn run_barrel_migration_wrapper(path: &Path) -> Box<Migration> { + let (up, down) = run_barrel_migration(&path); + let version = version_from_path(path).unwrap(); + let migration_path = match path.parent() { + Some(parent_path) => parent_path.to_path_buf(), + None => path.to_path_buf(), + }; + Box::new(BarrelMigration(migration_path, version, up, down)) +} + +fn run_barrel_migration(migration: &Path) -> (String, String) { + /* Create a tmp dir with src/ child */ + use tempfile::Builder; + + let dir = Builder::new().prefix("barrel").tempdir().unwrap(); + fs::create_dir_all(&dir.path().join("src")).unwrap(); + + let (feat, ident) = get_backend_pair(); + + let toml = format!( + "# This file is auto generated by barrel +[package] +name = \"tmp-generator\" +description = \"Doing nasty things with cargo\" +version = \"0.0.0\" +authors = [\"Katharina Fey <kookie@spacekookie.de>\"] +# TODO: Use same `barrel` dependency as crate +[dependencies] +barrel = {{ version = \"*\", features = [ {:?} ] }}", + feat + ); + + /* Add a Cargo.toml file */ + let ct = dir.path().join("Cargo.toml"); + let mut cargo_toml = File::create(&ct).unwrap(); + cargo_toml.write_all(toml.as_bytes()).unwrap(); + + /* Generate main.rs based on user migration */ + let main_file_path = &dir.path().join("src").join("main.rs"); + let mut main_file = File::create(&main_file_path).unwrap(); + + let user_migration = migration.as_os_str().to_os_string().into_string().unwrap(); + main_file + .write_all( + format!( + "//! This file is auto generated by barrel +extern crate barrel; +use barrel::*; + +use barrel::backend::{ident}; + +include!(\"{}\"); + +fn main() {{ + let mut m_up = Migration::new(); + up(&mut m_up); + println!(\"{{}}\", m_up.make::<{ident}>()); + + let mut m_down = Migration::new(); + down(&mut m_down); + println!(\"{{}}\", m_down.make::<{ident}>()); +}} +", + user_migration, + ident = ident + ) + .as_bytes(), + ) + .unwrap(); + + let output = if cfg!(target_os = "windows") { + Command::new("cargo") + .current_dir(dir.path()) + .arg("run") + .output() + .expect("failed to execute cargo!") + } else { + Command::new("sh") + .current_dir(dir.path()) + .arg("-c") + .arg("cargo run") + .output() + .expect("failed to execute cargo!") + }; + + let output = String::from_utf8_lossy(&output.stdout); + let vec: Vec<&str> = output.split("\n").collect(); + let up = String::from(vec[0]); + let down = String::from(vec[1]); + + (up, down) +} + +/// Uses the fact that barrel with diesel support is only compiled with _one_ feature +/// +/// The first string is the feature-name, the other the struct ident +fn get_backend_pair() -> (&'static str, &'static str) { + #[cfg(feature = "pg")] + return ("pg", "Pg"); + #[cfg(feature = "mysql")] + return ("mysql", "Mysql"); + #[cfg(feature = "sqlite3")] + return ("sqlite3", "Sqlite"); +} |