aboutsummaryrefslogtreecommitdiff
path: root/development/libs/barrel/src/integrations/diesel.rs
diff options
context:
space:
mode:
Diffstat (limited to 'development/libs/barrel/src/integrations/diesel.rs')
-rw-r--r--development/libs/barrel/src/integrations/diesel.rs197
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");
+}